home *** CD-ROM | disk | FTP | other *** search
/ TeX 1995 July / TeX CD-ROM July 1995 (Disc 1)(Walnut Creek)(1995).ISO / dviware / crudetype / version3 / crudetype.web (.txt) < prev    next >
LaTeX Document  |  1991-11-28  |  151KB  |  3,481 lines

  1. % CRUDETYPE.WEB   ADAPTED FROM DVITYPE, VERSION 2.6.
  2. % REVISIONS:
  3. % 9/86: clarify names of global variables, supply hooks for attempted
  4. %           Hewlett-Packard Laserjet version.
  5. % 1/88: Several bugfixes. Chiefly the noscheme bug (TFM files without coding
  6. %       schemes)
  7. %       Also, added some MATH EXTENSION character codes.
  8. % 4/88: Unix change file by P. King.
  9. % 10/88: Version 2. Changes include:
  10. % Read a command line; additional options; cleaner interface to operating
  11. % system; bugfixes.
  12. % 11/90:  Version 3.01. Minor bugfixes; a serious attempt to tackle character
  13. % strings; (from J.Warbrick) screenview; several more coding schemes;
  14. % additional options.
  15. % Here is TeX material that gets inserted after \input webmac
  16. \def\hang{\hangindent 3em\indent\ignorespaces}
  17. \font\ninerm=cmr9
  18. \let\mc=\ninerm % medium caps for names like PASCAL
  19. \def\PASCAL{{\mc PASCAL}}
  20. \def\WEB{{\mc WEB}}
  21. \def\(#1){} % this is used to make section names sort themselves better
  22. \def\9#1{} % this is used for sort keys in the index
  23. \def\title{Crudetype}
  24. \def\contentspagenumber{1}
  25. \def\topofcontents{\null
  26.   \def\titlepage{F} % include headline on the contents page
  27.   \def\rheader{\mainfont\hfil \contentspagenumber}
  28.   \vfill
  29.   \centerline{\titlefont Crudetype}
  30.   \vskip 45pt
  31.   \centerline{An adaptable device driver (Version 3, 1990)}
  32.   \vskip 45pt
  33.   \centerline{R.M.Damerell,} \vskip 20pt
  34.   \centerline{Mathematics Dept.,} \vskip 15pt
  35.   \centerline{Royal Holloway and Bedford College,} \vskip 15pt
  36.   \centerline{Egham, Surrey, U.K.} \vskip 15pt
  37. \vfill}
  38. \pageno=\contentspagenumber \advance\pageno by 1
  39. \let\maybe=\iffalse
  40. % These macros for verbatim scanning are copied from MANMAC.TEX. But we cant
  41. % use the vertical bar for a temporary escape character as WEAVE catches it.
  42. % So we will use ! instead and hope for the best
  43. \chardef\other=12
  44. \def\ttverbatim{\begingroup
  45.   \catcode`\\=\other  \catcode`\{=\other  \catcode`\}=\other  \catcode`\$=\other
  46.   \catcode`\&=\other  \catcode`\#=\other  \catcode`\%=\other  \catcode`\~=\other
  47.   \catcode`\_=\other  \catcode`\^=\other
  48.   \obeyspaces \obeylines \tt}
  49. \def\begintt{$$\let\par=\endgraf \ttverbatim \parskip=0pt
  50.   \catcode`\!=0 \rightskip-5pc \ttfinish}
  51. {\catcode`\!=0 !catcode`!\=\other   % ! is temporary escape character
  52.   !obeylines !obeyspaces    % end of line is active
  53.   !gdef!ttfinish#1^^M#2\endtt{#1!vbox{#2}!endgroup$$}}
  54. \def\up{\hbox{\tt{\char'013}}}
  55. \def\markarrow#1{\vtop{\hbox{#1}\up}}
  56. @* Introduction.
  57. COPYRIGHT ( C ) R.M.Damerell, 1988.
  58. Permission is given to any person to make and distribute copies of this
  59. software, subject to the following conditions:
  60. 1. All copies of the software must carry an exact copy of this notice.
  61. 2. This software is distributed free of charge, ``AS IS" with absolutely no
  62. guarantee of performance. Any persons receiving or using this software must do
  63. so entirely at their own risk. Neither the authors nor their institutions
  64. accept any liability for any defects of this software, or for any consequential
  65. loss or damage however caused.
  66. 3. Any person who changes this software must clearly mark it as modified and
  67. add a note describing the changes made.
  68. This is an experimental version and no guarantee of performance is given.
  69. I would like to receive bug reports, same address or electronic mail to
  70. DAMERELL at UK.AC.NSFNET-RELAY (From the USA, I believe that site is 
  71. NSFNET-RELAY.AC.UK). \par\vskip 0.5in
  72. This program was originally based on D.E.Knuth's program \.{DVItype}, but so
  73. many changes were needed for various reasons that there is hardly any of the
  74. original code left. The purpose of this program is to provide a framework for
  75. users to write \TeX\ device drivers for a variety of `crude' devices. Roughly
  76. speaking, `crude' means any printer that cannot print the fonts that Metafont
  77. generates. This would include daisy-wheels and most impact dot-matrix
  78. printers. Considered as output printers for \TeX, such devices usually have
  79. some of the following misfeatures: \item
  80. 1. Coarse resolution.\item
  81. 2. Restricted character set. \item
  82. 3. Some printers cannot do reverse line feeds, some can, and tear the paper.
  83. \item
  84. 4. Slow interface between CPU and printer.\par
  85. Although such printers cannot do justice to \TeX\ output, drivers for them
  86. are still needed. Some users cannot afford high quality printers. Some can
  87. only afford to use them for final output; so they need to make proofs on a
  88. cheaper printer. Also, anybody who has a high quality printer may well need
  89. to refer to various \.{WEB} files while writing a driver for it. These can
  90. become illegible in critical places. Here is a sample from \.{DVItype}:
  91. \begintt
  92. A |fix_word| whose respective bytes are $(a,b,c,d)$ represents the number
  93. $$x=\left\{\vcenter{\halign{$#$,\hfil\qquad&if $#$\hfil\cr
  94. b\cdot2^{-4}+c\cdot2^{-12}+d\cdot2^{-20}&a=0;\cr
  95. -16+b\cdot2^{-4}+c\cdot2^{-12}+d\cdot2^{-20}&a=255.\cr}}\right.$$
  96. \endtt
  97. Using the basic  (line printer) version of \.{Crudetype}, we can get a copy of
  98. these formulae which is at least legible, even though the result may not be at
  99. all pleasant to look at. A further difficulty with conventional drivers is
  100. that most of these use the algorithm `paint a page of pixels, send it down the
  101. line'. This places a heavy load on both the host computer and the link to the
  102. printer. Of course, one can try to reduce this load by various optimisations,
  103. (e.g. by writing critical bits of code in machine language) but this makes the
  104. program non-portable, and often introduces bugs. \.{Crudetype} is written
  105. entirely in \PASCAL, without any attempt at optimisation. When compiled on a
  106. VAX 780 with the NO-OPTIMISE, CHECK and DEBUG qualifiers it runs at about 2--3
  107. seconds a page. These times are highly variable, and the VMS optimiser reduces
  108. them by about 10-15\%.
  109. @ Printers vary enormously both in their capabilities and in the commands that
  110. drive them. The behaviour of \.{Crudetype} is controlled by a large number of
  111. constants, which supposedly describe how the target printer does things. This
  112. does have the disadvantage that the user must compile a separate copy of the
  113. program for each different printer, and also devise some way to ensure that he
  114. uses the right version for the intended printer. But the only alternative seemed
  115. to be that \.{Crudetype} should read and parse a file describing the printer and
  116. this appeared to be unbearably messy.  In Version 3, many of these constants are
  117. now variables. This means that you can (to a limited extent) support different
  118. printers by flags in the command line.  Ideally, these constants should be so
  119. designed that:
  120. \item
  121. 1. Any decent printer can be driven by assigning the right values to these
  122. constants and recompiling. \item
  123. 2. If the printer is properly documented, it should be immediately obvious
  124. what are the correct values for all these constants.
  125. At present I do not have enough experience of different printers to come near
  126. this ideal. In particular, some printers can download characters. The
  127. problems of writing a program to support this facility in proper generality
  128. are horrible and ghastly. I have not made any serious attempt yet to tackle
  129. them. There are just a few places where a hook appears, and I hope eventually
  130. to attach actual routines for downloading.
  131. Some of the more obvious problems of downloading are: when can you download?
  132. At any time? start of page? or only at start of document? Can you load one
  133. character, or must you load a whole font at a time? How much memory does the
  134. printer provide for downloading? How efficiently does it use its memory? What
  135. does it do when it runs out? Can you clear out old fonts to make more space?
  136. What is the format of a down-load command? What parameters does it need, in
  137. what order, with what punctuation? In what order must pixels be sent? Should
  138. they be compressed, and how?
  139. @ Implementation.
  140. The original version of \.{Crudetype} was aimed at a line printer, (because
  141. everybody has these) and was written on the VAX-VMS operating system. The
  142. intention is that this program should be easily adaptable both to other
  143. systems and to other printers. So most of it is written in Standard \PASCAL.
  144. (It is not possible to tell exactly how much of it is Standard, as we do not
  145. have a certified compiler.) But in some places, it is necessary to use
  146. extensions. In particular, \.{Crudetype} must read the font files, whose names
  147. are dynamically specified. That would be impossible in pure \PASCAL.
  148. \.{Crudetype} also uses non-Standard code in order to talk to the user's
  149. terminal. It asks for the name of the \.{DVI} file, and for the first page and
  150. the number of pages to print. Alternatively, it can read a `command line'
  151. and do simple-minded parsing. If an operating system forbids terminal
  152. interaction, the installer will have to find another way to give the program
  153. this information. As file handling is inevitably system-dependent, I have here
  154. allowed myself a lot of latitude in using VMS-specific procedures. If
  155. \.{Crudetype} cannot find a file, it will ask the user for another name. On
  156. the other hand, all files are read and written sequentially, and I have got
  157. rid of all uses of the default |case| statement. The intention is that all the
  158. system-dependent stuff goes near the top of the file, and all
  159. printer-dependent stuff at the end. Then with any luck you can merely
  160. concatenate Change files for the local system and the local printer, instead
  161. of having to merge them. All the code that is known to be non-Standard has
  162. been carefully segregated from the rest of the program. It amounts to about 20
  163. lines out of 750.
  164. @^System dependencies@>
  165. It is clearly impossible to predict what difficulties will appear in trying
  166. to install \.{Crudetype} on other systems, it would seem to be advisable to
  167. get the line printer version working before trying to adapt it for any other
  168. printer. To try to ease the process, I propose to distribute several test
  169. files with the program. These are of the form SAMPLE.TEX, SAMPLE.DVI and
  170. SAMPLE.PRI (the line printer output).
  171. Although `crude' printers differ very much in their capacities, one thing
  172. they nearly all have in common is that they cannot feed the paper backwards.
  173. Some printers cant |Backfeed| at all; some tear the paper, and others let the
  174. paper slip and so lose position. Therefore it seems to be essential to process
  175. each page as follows: first copy the page into a suitable structure, then sort
  176. it by vertical and horizontal position, then print it.
  177. Change files have been written for other systems: Unix (by P.King), NOS/VE
  178. (G-H.Knauf and M.Rawohl), and Primos (J.Warbrick). Many of the changes that
  179. these authors made are not system dependencies but improvements to the basic
  180. program. I have tried to incorporate these into the current version, and I
  181. want to thank them for their contributions.
  182. @* Main Program.
  183. @d banner=='This is Crudetype, Version 3.01, copyright, experimental'
  184. {printed when the program starts}
  185. @p program crudetype
  186.   @<Declarations@>
  187.   begin
  188.     @<Initialize@>;
  189.     repeat
  190.       @<For each page of \.{DVI}, print it if desired@>
  191.     until time_to_stop ;
  192.     @<Clean up afterwards@>;
  193.   exit: end.
  194. @ Now here are some of the messy things we must do to satisfy the rules of
  195. \PASCAL.
  196. @<Declarations@>=
  197.   (@<Files@>) ;
  198.   label exit ;
  199.   const @<Constants in the outer block@>
  200.   type @<Types in the outer block@>
  201.   var @<Globals in the outer block@>
  202.   @<Forward and external declarations@>
  203.   @<Lowest level procedures@>
  204.   @<Medium level procedures@>
  205.   @<Top level procedures@>
  206. @ @<Initialize@>=
  207.   @<Set |blank|@>
  208.   @<Set initial values@>
  209.   @<Open terminal channels@>
  210.   @<Determine operating parameters@>
  211.   @<Assign character codes@>
  212.   @<Set up for the printer@>
  213.   if not quiet then
  214.     display_ln(banner, ' --- ', device_ID) ;
  215.   @<Open |printfile|@>;
  216.   @<Read \.{DVI} preamble@>
  217. @ @<Glob...@>=
  218.   in_i, in_j :integer; {loop index for initializations}
  219. @ Next, here are some macros for common programming idioms.
  220. @d incr(#) == #:=#+1 {increase a variable by unity}
  221. @d decr(#) == #:=#-1 {decrease a variable by unity}
  222. @d do_nothing == {empty statement}
  223. @d exit == 732
  224. @d return == goto exit
  225.  {Go here when a loop ends abnormally}
  226. @ The next two procedures are very primitive debugging aids.  All internally
  227. detected errors call |error|. Then they can be caught by suitable debugger
  228. commands. When \TeX\ sees a fatal error, it calls a procedure |jump_out| which
  229. jumps to a label at the ``|end.|'' of the program. This makes debugging much
  230. more difficult, because the program has ended normally, so it is impossible to
  231. interrogate variables, etc. So the earlier versions of \.{Crudetype} were
  232. designed to crash on a fatal error. This action is unpopular; I hope that fatal
  233. errors are now rare enough that we do not need to crash any longer.
  234. @<Forward...@>=
  235. procedure error   ; begin end;
  236. procedure crash;
  237. var u: real;
  238. begin
  239.   u := -1 ;
  240.   error;
  241.   if ( u<0) then goto exit ;
  242. @* Interface to Operating System, 1: System dependent code.
  243. The purpose of these sections is to try to give a reasonable interface between
  244. the operating system and the rest of the program, which is supposed to be
  245. Standard \PASCAL. Nearly all the non-Standard code is concerned with file
  246. handling and the lowest level of I/O. This is an area where Standard \PASCAL\
  247. seems to be particularly weak. \.{Crudetype} was originally written for VMS,
  248. but as change files have started to appear, it has become clear that all the
  249. most VMS-specific code really ought to be put into a Change file. So this
  250. section contains a lot of dummy declarations. The actual declarations are
  251. expected to be found in Change files. It is hoped that most of the later
  252. sections will work on a wide range of machines. Everything here is system
  253. dependent, so there is no point in indexing each module separately.
  254.  @^System dependencies@>
  255. @ Here are some system-dependent types and constants. |integer| should be
  256. 32-bits, and |real_number| should probably be 64. Normally, I use |integer|
  257. whenever the bit length is unimportant, but I use subranges in the
  258. |page_record| type, as this allows packing and may improve the program's
  259. performance.
  260. @d real_number == real
  261. @d make_real( #) == #
  262.     {convert an integer to a |real_number|. Usually automatic}
  263. @d  max_half = 32767
  264. @<Types...@>=
  265.   byte = 0..255 ;
  266.   i_word = -max_half-1 .. max_half ;
  267. @ Characters and strings. I have here deleted all the code from \.{DVItype}
  268. that translates from characters to small integers and back.  This is because
  269. we have to do a quite different translation anyway. If it is necessary to put
  270. that code back in, then it will probably be necessary to insert \begintt
  271.  define zchr(#) == xchr[#] \endtt
  272. because of the different brackets. 
  273. Strings are represented internally by the |var_string| type and nearly all the
  274. code that uses these is Standard. There are 2 non-Standard things we need to do
  275. with strings: (1) convert a quoted string to a |var_string|. (2) convert a
  276. |var_string| into whatever type the local \PASCAL\ accepts as a file name.
  277. Because Standard \PASCAL\ has no decent mechanism to deal with quoted strings,
  278. every manufacturer has had to make non-Standard extensions to get a useful
  279. compiler. The main alternatives are: conformant arrays, blank-padding, and
  280. variable-length. The \WEB\ adopts conformant arrays because these are a ``level
  281. one'' feature of the Standard. But you can adopt other mechanisms by altering
  282. the macros and constants defined here. See various change files for examples.
  283. @d zchr == chr
  284. @d zord == ord
  285. @d Q_string == packed array[ first..last:integer] of char
  286. @d be_string(#) == set_string( #, buffer)
  287. @d set_j_to_length == j := last 
  288. @<Forw...@>=
  289.   procedure set_string(
  290.       ss: Q_string; var result: var_string ) ; forward;
  291. @ These constants affect the way character strings get handled. They are
  292. described under ``character strings''. 
  293. @<Const...@>=
  294.   padded = true ;
  295.   pad_char = ' ' ;
  296.   amp_and = '&' ;
  297.   ctrl_flag = '^' ;
  298. @ Now consider file names. As in \TeX, we assume that these have 3 parts:
  299. (directory)(name)(extension). The change file must define a procedure
  300. |@!parse_file| which chops a filename |name| into its components, called |dir|,
  301. |nam|, and |ex|. The procedure |name_of| must convert a |var_string| into
  302. whatever type the local \PASCAL\ accepts as a file name.
  303. @d filename == fix_string
  304. @d n_len == max_string
  305. @<Lowest...@>=
  306.   {Declare |parse_file|}
  307.   procedure name_of( var result: filename; name: var_string) ;
  308.   var n, i: integer;
  309.   begin
  310.     n := name.len ;
  311.     if ( n > n_len) then n := n_len ;
  312.     for i := 1 to n do result[ i] := name.data[ i] ;
  313.     for i := n+1 to n_len do result[ i] := ' ' ;
  314.   end;
  315. @ In this section we generate a name for the printed file, unless the user
  316. specified one. Essentially, this involves deleting the extension part of the
  317. \.{DVI} filename and adding a new one (in |@!print_ex|). If |@!same_dir|, put
  318. the file into the \.{DVI} file's directory.
  319. @<Open |printfile|@>=
  320.   if ( print_name.len = 0) then begin
  321.     parse_file( dvi_name, p_d, p_n, p_ex);
  322.     if same_dir then print_name := p_d
  323.     else print_name := blank ;
  324.     append( print_name, p_n );
  325.     append( print_name, print_ex) ;
  326.   end;
  327. @ The next few sections contain the lowest level code for file handling. These
  328. macros describe how we use the terminal.
  329. In V3, we have tried to implement a screen view version. To work this,
  330. we have to pause the output at times. on Unix and on VMS we had to do 
  331. peculiar things to synchronise the terminal and printer streams. The
  332. macro |@!flush_out| works on VMS; see also the Unix changefile, under
  333. |inspection|.
  334. @d flush_out == begin print_ln ; display_ln(' '); end
  335. @d term_in==input {the terminal, considered as an input file}
  336. @d term_out==output {and output}
  337. @d can_interact == true
  338. @d i_reset_terminal == do_nothing    {Switch terminal to input}
  339. @d o_rewrite_terminal == do_nothing  {and back to output}
  340. @d display(#)==write(term_out, #)
  341. @d display_ln(#)==write_ln(term_out, # )
  342. @d print(#)==write(printfile, #)
  343. @d print_ln ==write_ln(printfile )
  344. @d string_show( #) == print_string( term_out, #, ' ')
  345. @d string_print( #) == print_string( printfile, #, ctrl_flag )
  346. @d warn (#)==begin display_ln('Error: ', #); error; end
  347. @d abort(#)==begin display_ln('Fatal: ', #); crash; end
  348. @d bad_dvi(#)==abort('Bad DVI file: ',# )
  349. @<Open terminal channels@>= do_nothing
  350. @ As an initial attempt at downloading, we declare a |@!raster_file|.
  351. @<Files@>= term_in, term_out, printfile, dvi_file, tfm_file, raster_file
  352. @ Here we define some system-dependent properties of files. \.{Crudetype}
  353. tries to search for files in a sensible way by using default names. Note that
  354. the default names should not contain wild cards for their missing bits.
  355. Several different patterns have been invented for raster file names. The
  356. default name |raster_def| contains the substring |'&D'|. This is put in to
  357. be replaced by the calculated magnification when we try to open the file.
  358. @d block_length = 512
  359. @d same_dir == false
  360. @<Set init...@>=
  361.   be_string( '.DVI' ) ; dvi_def := buffer ;
  362.   be_string( 'TEX$FONTS:.TFM' ) ; tfm_def := buffer ;
  363.   be_string( 'TEX$GF:.&DGF' ) ; raster_def := buffer ;
  364.   be_string( '.PRI' ) ; print_ex := buffer ;
  365. @ |@!open_binary| is the lowest-level procedure for opening binary files. If
  366. possible, it must try to open a file called |name| and not crash if the file
  367. cannot be opened. Return true or false to indicate success.
  368. @<Lowest...@>=
  369.   {Declare |open_binary|}
  370. @ @<Open |printfile|@>=
  371.   rewrite(printfile) ;
  372. @ \.{Crudetype} tries to read a ``command line''. |@!read_command_line| should
  373. be the procedure that actually reads the line, and these macros extract pieces
  374. of it. The code below should work on systems that cannot read command lines.
  375. @d get_val( #) == # := s_to_i( #, true)
  376. @d prefix == "/"
  377. @d got_cl == ( command.len > 0)
  378. @d read_command_line( #) == do_nothing
  379. @<Lowest...@>=
  380.   {Declare |read_command_line| }
  381.   procedure get_command ;
  382.   var ss: fix_string ;
  383.   begin
  384.     ss := blank.data ;
  385.     read_command_line( ss) ;
  386.     be_string( ss ) ; command := buffer ;
  387.   end;
  388. @ Here are macros for the adaptable merge sort. See the section on sorting for
  389. explanation.
  390. @d image(#) == pool[#]
  391. @d create == incr(cell)
  392. @d link_type == page_i
  393. @d first_cell == cell := 0
  394. @d wipe_out(#) ==
  395. @d declare_pool ==  pool: array [page_i] of page_record;
  396. @d garbage == cell := zzz ;
  397. @* Interface to Operating System, 2: Dialog with Environment.
  398. In this section, \.{Crudetype} will determine what it is supposed to be doing.
  399. It might be called either interactively, by a command like this:  \begintt
  400. (RUN) crudetype  \endtt
  401.   or in batchmode by a command like this:\begintt
  402. (RUN) crudetype (parameters)\endtt
  403.   In the first case, \.{Crudetype} must ask the user for all its operating
  404. parameters. In the second, it must read the parameters from the command line
  405. and supply defaults for all the missing ones. If the local operating system
  406. does not allow either of these methods, the installer will have to devise some
  407. other way to supply the data.
  408. If there is a command line, the system-dependent procedure |@!get_command|
  409. should fetch it and put it into |command|; then |parse_command| will read it.
  410. @<Determine operating parameters@>=
  411.   command := blank ; get_command ;
  412.   dvi_name := blank ;
  413.   if got_cl then parse_command ;
  414.   @<Get \.{DVI} file name and open it@> ;
  415.   if not got_cl then @<Ask the user@> ;
  416. @ The main argument is the input (\.{DVI}) file name. All other arguments are
  417. optional, and have the form (prefix)(key)(value). The |@!prefix| can be any
  418. character reserved for that purpose (`/' in VMS, `-' in Unix, etc). The
  419. keyword is one letter, in upper or lower case. The value is usually an
  420. integer. The permitted keys are:
  421. \item {\tt /p} |printfilename| to redirect output, 
  422. \item {\tt /q} to suppress the information messages, 
  423. \item {\tt /r} to suppress form feeds in the output, 
  424. \item {\tt /s} to suppress blank lines, 
  425. \item {\tt /i} attempts a screen view, (i stands for `|inspection|')
  426. \item {\tt /b} attempts a `batch' view version. The difference between these 
  427. is that {\tt /i} tries to give immediate output on the terminal and {\tt /b} 
  428. puts it in a file to be examined later. 
  429. \item {\tt /m} (number) to give the magnification, 
  430. \item {\tt /x} (number) to give a horizontal magnification, 
  431. \item {\tt /y} (number) to give a vertical magnification, 
  432. \item {\tt /d} (directory) specifies an alternative font directory,
  433. \item {\tt /f} (number) to give the first page number, 
  434. \item {\tt /c} (number) to give the maximum number of pages to print. 
  435. \item {\tt /e}, {\tt /o, /l } are reserved for use by certain changefiles. 
  436. Here are the defaults:
  437. @<Set init...@>=
  438.   quiet := false ;
  439.   run_on := false ;
  440.   squash := false ;
  441.   magnify := 100 ;
  442.   h_mag := 100 ;
  443.   v_mag := 100 ;
  444.   batch_view := false ;
  445.   first_page := -1000000 ;
  446.   count_pages := 1000000 ;
  447.   print_name := blank ;
  448. @ @<Glob...@>=
  449.   command: var_string ;
  450.   squash, run_on, quiet, batch_view: boolean;
  451.   magnify, h_mag, v_mag, first_page, count_pages : integer ;
  452. @ If either {\tt /x} or {\tt /y} is present as well as {\tt /m}, they
  453. both take effect.  Note these numbers are percents; default = 100
  454. means use the mag. specified in the \.{DVI} file.  {\tt /v} implies
  455. {\tt /s} and {\tt /r}, and it also alters the vertical magnification
  456. and suppresses overstriking.
  457. @<If the |key| is valid, do the corresponding command@>=
  458.   if ( key = "Q") then quiet := true
  459.   else if ( key = "S") then squash := true
  460.   else if ( key = "R") then run_on := true
  461.   else if ( key = "P") then get_name( print_name)
  462.   else if ( key = "D") then get_name( tfm_def)
  463.   else if ( key = "F") then get_val( first_page)
  464.   else if ( key = "C") then get_val( count_pages)
  465.   else if ( key = "M") then get_val( magnify)
  466.   else if ( key = "X") then get_val( h_mag)
  467.   else if ( key = "Y") then get_val( v_mag)
  468. @ If there was no command line, try to send messages to the user at a
  469. terminal. This requires nonstandard \PASCAL\ constructions to handle the
  470. online interaction. So it may be necessary on some systems to omit the dialog.
  471. First, the \.{DVI} file name.
  472. @<Get \.{DVI} file name and open it@>=
  473.   if can_interact and ( dvi_name.len = 0) then repeat
  474.     ask_prompt('DVI file name? ') ;
  475.     get_name( dvi_name) ;
  476.   until ( dvi_name.len > 0) ;
  477.   if not open_and_ask(dvi_file, dvi_indx, dvi_name, dvi_def, true)
  478.   then abort('Couldnt open DVI file')
  479.     @.Fatal: Couldnt open@>
  480. @ But when we come to open a font file, we merely report a failure:
  481. @<Open font file@>=
  482.   font_ok := open_and_ask (tfm_file, tfm_indx, tfm_name, tfm_def, true) ;
  483. @ Nobody enjoys filling in forms; so we only ask a few parameters in
  484. interactive mode. Most characters in \TeX\ fonts are narrower than
  485. line-printer characters. So we must spread them out to make them fit.
  486. Originally, this was done by multiplying \.{DVI} distances by a constant
  487. factor |h_fudge|. This is all right for one size of type but it tends to fail
  488. for other sizes because if the predominant type size is larger than expected,
  489. then rounding with a constant factor makes everything\qquad\ very\qquad\
  490. badly\qquad\ spread\qquad\ out. It seemed that the least bad way to tackle
  491. this is to allow the user to specify an extra magnification factor.
  492. @^magnification@>
  493. @<Ask the user@>=
  494.   if can_interact then begin
  495.     buffer := blank ;
  496.     ask_prompt('First page? (default = print ALL pages) ' );
  497.     if ( buffer.len > 0) then get_val( first_page) ;
  498.     ask_prompt('Maximum no. of pages? (default = 1000000) ' ) ;
  499.     if ( buffer.len > 0) then get_val( count_pages) ;
  500.     ask_prompt (
  501.       'What magnification? (integer, percent, default=100%=DVI file mag)1') ;
  502.     if ( buffer.len > 0) then get_val( magnify) ;
  503.   end;
  504.     @.First page?@>@.Max. no. of pages?@>@.What magnification?@>
  505. @ Since the terminal is being used for both input and output, some systems
  506. need a special routine to make sure that the user can see a prompt message
  507. before waiting for input based on that message. (Otherwise the message may
  508. just be sitting in a hidden buffer somewhere, and the user will have no idea
  509. what the program is waiting for.) Here, we assume that the system-dependent
  510. macros |@!i_reset_terminal| and |@!o_rewrite_terminal| (defined above) will do
  511. whatever is necessary to switch the terminal from output to input and back. We
  512. assume that the terminal is normally in output mode, and call these macros
  513. when we want input. If the system does not allow this, then |@!can_interact|
  514. should be set false.
  515. Here is how the program prompts for input: the argument of |ask_prompt| is the
  516. prompt text. Because of the anomalous behaviour of |write|, this ought to work
  517. with arguments of any length, even on versions of \PASCAL\ that only allow
  518. fixed length strings.
  519. @d ask_prompt(#) == begin
  520.   display_ln(#) ; read_terminal ; end;
  521. @<Lowest...@>=
  522.   procedure read_terminal;
  523.   var k: byte ;
  524.   begin i_reset_terminal;
  525.     buffer := blank ;
  526.     if not eof(term_in ) then begin
  527.       if eoln(term_in) then read_ln(term_in);
  528.       k:=0;
  529.       while( not eoln(term_in)) do
  530.       begin incr(k); buffer.data[k]:=term_in^; get(term_in);
  531.       end;
  532.       buffer.len := k ;
  533.       finger := 0 ; get_char ;
  534.     end;
  535.     o_rewrite_terminal ;
  536.   end;
  537. @ If the printer is actually a VDU, then possibly the user will want to pause
  538. at intervals.
  539. @<Check pause@>=
  540.   if do_pause then begin
  541.       incr(pause_i) ;
  542.       if  (pause_i >= pause_steps) then begin
  543.           flush_out ;
  544.           display(pause_ask);
  545.           i_reset_terminal;
  546.           read_ln (term_in );
  547.           o_rewrite_terminal ;
  548.           string_show(pause_after);
  549.           pause_i := 0 ;
  550.         end;
  551.     end;
  552. @ @<Const...@>=
  553.   pause_ask = 'PAUSED. Type <return> to continue' ;
  554. @* Interface to Operating System, 3: Input from binary files.
  555. The main input file is the \.{DVI} file. Logically, this is just a stream of
  556. 8-bit bytes, with no record or block structure. However VMS \PASCAL\
  557. apparently cannot handle files of this type; so I have adopted the blocking
  558. scheme (due to D.R.Fuchs) from the VMS \.{DVItype} change file. But a lot of
  559. the code has been rewritten. Some other operating systems use similar
  560. blocking schemes; so this code may possibly work without much change. The
  561. program deals with two binary file variables: |@!dvi_file| is the main input
  562. file that we are printing, and |@!tfm_file| the current font metric file from
  563. which character-width information is being read. Each of these has a name and
  564. a counter, declared here; also a default name (system dependent, and so
  565. declared previously).
  566. @^Fuchs, D.R.@>
  567. @<Types...@>=
  568.   @!byte_block=packed array [0..block_length-1] of byte ;
  569.   @!byte_file= packed file of byte_block;
  570. @ @<Glob...@>=
  571.   dvi_file, tfm_file, raster_file: byte_file ;
  572.   dvi_indx, tfm_indx, raster_indx: integer ;   {Block pointers}
  573.   dvi_name, tfm_name, raster_name, print_name,  {File names}
  574.   dvi_def, tfm_def, raster_def, print_ex : var_string ;    {and default names}
  575.   font_name, p_n, p_d, p_ex : var_string ;
  576.     {Scratch variables for assembling names}
  577.   printfile: text ;
  578. @ Here is the procedure that actually opens binary files. It searches for a
  579. file called |name|, supplying missing bits from the default file-specification
  580. in |other_name|. |f_f| is the file being opened, and |f_c| is its counter.
  581. @<Medium...@>=
  582.   function open_and_ask
  583.   (var f_f: byte_file; var f_c: integer; var name,
  584.     default: var_string; ask: boolean) : boolean ;
  585.   var success, fail: boolean;
  586.   def_dir, def_nam, def_ex, try_dir, try_nam, try_ex: var_string ;
  587.   begin
  588.     success := false; fail := false ;
  589.     repeat
  590.       @<Assemble the |name|@> ;
  591.       success := open_binary(f_f, name ) ;
  592.       if success then f_c := 0
  593.       else @<Try another name@>
  594.     until success or fail ;
  595.     open_and_ask:= success ;
  596.   end;
  597. @ @<Assemble the |name|@>=
  598.   parse_file( default, def_dir, def_nam, def_ex) ;
  599.   parse_file( name, try_dir, try_nam, try_ex) ;
  600.   if ( try_dir.len = 0) then name := def_dir
  601.   else name := try_dir ;
  602.   append ( name , try_nam);
  603.   if ( try_ex.len = 0) then append ( name , def_ex)
  604.   else append ( name, try_ex) ;
  605. @ If this fails, then ask the user for another name. If the operating system
  606. forbids this, or if the user refuses, then indicate a failure.
  607. @<Try another name@>=
  608.   if ask then begin
  609.       display('Couldnt open file: ' );
  610.       string_show(name) ;
  611.       display_ln (' ') ;
  612.       if can_interact then begin
  613.           ask_prompt('Please type a replacement or NO to abandon search' ) ;
  614.           name := buffer ;
  615.           if ( (name.len = 2) and
  616.               ( (name.data[1] = 'N') or (name.data[1] = 'n'))
  617.               and ( (name.data[2] = 'O') or (name.data[2] = 'o')))
  618.           then fail := true;
  619.         end else fail := true;
  620.     end else fail := true;
  621.     @.Couldnt open file@>@.Please type...@>
  622. @ \.{DVItype} has seven functions for reading integers from the \.{DVI} file
  623. and two more for the \.{TFM} file. I have condensed these. In order for
  624. these procedures to work, they must all have as parameters both the file and
  625. its attached counter. These macros generate the procedure calls.
  626. @d read_end(#) == # @=)@>
  627. @d skip(#) == skip_bytes @=(@> # @& file, # @& indx, read_end
  628. @d get_integer(#) == read_integer @=(@> # @& file, # @& indx, read_end
  629. @d get_byte(#) == read_byte(# @& file, # @& indx)
  630. @d get_real(#) == read_real(# @& file, # @& indx)
  631. @<Lowest...@>=
  632.   function read_byte(var f_file: byte_file; var f_indx: integer) : byte;
  633.   begin
  634.     if eof(f_file) then
  635.       warn('End of file' )
  636.     else begin
  637.         read_byte := f_file^[f_indx] ;
  638.         incr(f_indx);
  639.         if f_indx =block_length then begin
  640.             get(f_file );
  641.             f_indx:=0;
  642.           end;
  643.       end;
  644.   end ;
  645.   procedure skip_bytes(var f_file: byte_file; var f_indx: integer; n:integer);
  646.     {discard n bytes from |f_file|}
  647.   begin
  648.     if n < 0 then abort('Skip_bytes called with negative number');
  649.     f_indx := f_indx + n;
  650.     while( f_indx >= block_length) do
  651.       begin
  652.         if eof(f_file) then
  653.           warn('End of file' )
  654.         else get(f_file );
  655.         f_indx := f_indx - block_length ;
  656.       end ;
  657.   end;
  658.     @.Error: End of file@> @.Fatal: Skip_bytes...@>
  659. @ The next function reads an integer from a file. |k| specifies the type.
  660. |abs(k)| is the number of bytes, and the integer will be signed if |k<0|.
  661. @<Lowest...@>=
  662.   function read_integer
  663.     (var f_file: byte_file; var f_indx: integer; k: integer): integer;
  664.     var a, i : byte; n: integer;
  665.     begin n := get_byte(f );
  666.       if (k < 0) and (n > 127) then n := n-256 ;
  667.       for i := 1 to abs(k) - 1 do
  668.       begin
  669.         a := get_byte(f ) ;
  670.         n := n*256 + a ;
  671.       end ;
  672.       read_integer := n ;
  673.     end;
  674. @ A real number is stored in the file as 2 integers, numerator first.
  675. @<Medium...@>=
  676.   function read_real(var f_file: byte_file; var f_indx: integer ): real_number;
  677.   var a, b: integer;
  678.   begin a := get_integer(f )(-4);
  679.     b :=  get_integer(f )(-4);
  680.     if b <= 0 then
  681.     begin
  682.       warn('Denominator <= 0! '); read_real:= 1;
  683.     end
  684.     else read_real:= make_real(a)/make_real(b) ;
  685.   end;
  686.     @.Error: Denominator...@>
  687. @* Page selection.
  688. We have now disposed of all the code that is known to be system-dependent, so
  689. we can resume a proper top-down description of the program. The basic method
  690. for processing each page is that all printable characters are written onto a
  691. structure called a `page image'. This is a list of things called `page
  692. records'. Each page record represents one printable character, and contains
  693. two fields giving the intended position on the page. Eventually the image will
  694. be sorted and then copied to the |printfile|. This means that \.{Crudetype}
  695. has to remember three sets of coordinates. In order to help to keep track of
  696. many global variables, we use prefixes. \.{DVI} variables are prefixed with
  697. |D_|, page image variables with |IM_|, and the printer's variables with |PR_|.
  698. When this module starts, the \.{DVI} file should be positioned at or before a
  699. @<For each page...@>=
  700.   read_BOP;
  701.   if (counter[0] >= first_page) then start := true ;
  702.   if start and (count_pages > 0 )
  703.   then begin
  704.     @<Maybe a formfeed@>
  705.     decr(count_pages);
  706.     if not quiet then display('[', counter[0]:1 ); {Progress report}
  707.     Read_one_page ;
  708.     @<Sort the page@>
  709.     Send_page ;
  710.     if not quiet then display( ']' );
  711.   end
  712.   else if ( count_pages > 0) then Skip_page
  713.   else time_to_stop := true;
  714. @ This program only gives a small subset of the page-selection facilities of
  715. \.{DVItype}. The most you can do is to specify the starting page and the
  716. maximum number of pages to print. This will be controlled by these variables:
  717. @<Glob...@>=
  718.   start, time_to_stop, page_gap: boolean;
  719.   counter: array[0..9] of integer ;
  720. @ @<Set init...@>=
  721.   start := false ; time_to_stop := false; page_gap := false ;
  722.   for in_i := 0 to 9 do counter[ in_i ] := 0 ;
  723. @ |@!D_com| is the \.{DVI} command byte, |@!D_par| its first parameter.
  724. @<Top level...@>=
  725.   procedure Read_one_page ;
  726.     var D_com: byte; D_par: integer; end_page: boolean ;
  727.   begin end_page := false;
  728.     @<Set up an empty page image and |push| the position@>
  729.     repeat
  730.       @<Get \.{DVI} command |D_com|, and do it@>
  731.     until end_page;
  732.     pop ;
  733.   end ;
  734. @#procedure Skip_page ;
  735.     var D_com: byte; D_par: integer; end_page: boolean ;
  736.   begin
  737.     end_page := false;
  738.     repeat
  739.       @<Skip \.{DVI} command, but we must process any |font_def|@>
  740.     until end_page;
  741.   end ;
  742. @* Translating the device-independent file, 1: The big switch.
  743. Refer to \.{DVItype} or to \.{TUG}boat (Vol.3, No.2) for a description of the
  744. \.{DVI} file format. As in \.{DVItype}, we process each \.{DVI} command via a
  745. big |case| statement. But 192 of the cases are very similar, so lets dispose
  746. of them first.
  747.  @.TUGboat@>
  748. @d id_byte=2 {identifies the kind of \.{DVI} files described here}
  749. @d move_right ==
  750.     D_h := D_h + D_dis ;
  751.     IM_h := IM_h + IM_dis
  752. @<Get \.{DVI} command...@>=
  753.   D_dis := 0 ;  IM_dis := 0 ;
  754.   D_com := get_byte(dvi);
  755.   if D_com < 128 then begin
  756.     set_character(D_com); move_right ;
  757.   end
  758.   else if (D_com >= 171) and (D_com <= 234) then
  759.     change_font(D_com - 171)
  760.   else
  761. @ @<Skip \.{DVI} command...@>=
  762.   D_com := get_byte(dvi);
  763.   if (D_com < 128)
  764.   or ((D_com <= 234) and (D_com >= 171))
  765.   then do_nothing
  766.   else
  767. @ Now we come to the |case| statement proper. This section of the program is
  768. long and complicated, and I have tried to clean it up. Some commands want an
  769. unsigned parameter, called |D_par|, to be read from the file. We use
  770. |four_cases| for those. Others want a signed parameter; they are all
  771. movements. We use |move_cases| for those.
  772. @d four_case_end(#) == # ; end
  773. @d four_cases(#)==
  774.   #,#+1,#+2,#+3: begin D_par := get_integer(dvi)( D_com - # + 1 );
  775.     four_case_end
  776. @d move_cases(#)==
  777.   #,#+1,#+2,#+3: begin D_par := get_integer(dvi)( # - D_com - 1 );
  778.     four_case_end
  779. @<Get \.{DVI} command...@>=
  780. case D_com of
  781.   four_cases(128)    (set_character(D_par); move_right );
  782.   132:                begin set_rule; move_right ; end;
  783.   four_cases(133)    (set_character(D_par) );
  784.   137:                set_rule ;
  785.   138:                do_nothing ;
  786.   140:                end_page := true ;
  787.   141:                push;
  788.   142:                pop;
  789.   move_cases(143)    (D_h := D_h+D_par);
  790.   147:{W0}            D_h := D_h+D_w ;
  791.   move_cases(148)    (D_w := D_par; D_h := D_h+D_w );
  792.   152:{X0}            D_h := D_h+D_x ;
  793.   move_cases(153)    (D_x := D_par; D_h := D_h+D_x );
  794.   move_cases(157)    (move_down(D_par));
  795.   161:{Y0}            move_down(D_y);
  796.   move_cases(162)    (D_y := D_par; move_down(D_y) );
  797.   166:{Z0}            move_down(D_z);
  798.   move_cases(167)    (D_z := D_par; move_down(D_z) );
  799.   four_cases(235)    (change_font(D_par) );
  800.   four_cases(243)    (define_font(D_par) );
  801.   @<Fourteen illegal cases: print suitable error messages@>
  802. end ;
  803. @ When skipping a page, we must throw away parameters instead of using them.
  804. @d four_throw(#) ==
  805.   #,#+1,#+2,#+3: skip(dvi)(D_com - # + 1 )
  806. @<Skip \.{DVI} command...@>=
  807. case D_com of
  808.   four_throw(128);
  809.   132, 137: skip(dvi)(8); {sizes of a rule}
  810.   four_throw(133);
  811.   138:                ;
  812.   140:                end_page := true ;
  813.   141,142:            ;
  814.   four_throw(143);
  815.   147:                ;
  816.   four_throw(148);
  817.   152:                ;
  818.   four_throw(153);
  819.   four_throw(157);
  820.   161:                ;
  821.   four_throw(162);
  822.   166:                ;
  823.   four_throw(167);
  824.   four_throw(235);
  825.   four_cases(243)    (define_font(D_par) );
  826. @<Fourteen illegal...@>
  827. end ;
  828. @ Finally, there are 14 illegal values of |D_com| that generate various errors.
  829. @<Fourteen illegal...@>=
  830.   four_cases(239)
  831.     (warn('Cant do Special commands') ; skip(dvi)(D_par) );
  832.   139, 247, 248, 249:
  833.     bad_dvi('Byte: ', D_com:1 , ' out of context inside page' ) ;
  834.   250,251,252,253,254,255:
  835.     bad_dvi('Illegal command byte, ', D_com ) ;
  836.       @.Error: Cant do Special@>
  837.       @.Fatal: Bad DVI: Byte out of context@>
  838.       
  839. @* Translating the device-independent file, 2: Paging and the stack.
  840. The definition of \.{DVI} files refers to six registers, (|D_h, D_v, D_w,
  841. D_x, D_y, D_z|), which hold integer values in \.{DVI} units. We shall need
  842. additional registers in order to calculate a rounded position. From time to
  843. time, we save the current values of these on a stack, represented by the
  844. following arrays.
  845. @d max_stack = 200 {\.{DVI} files shouldn't |push| beyond this depth}
  846. @<Glob...@>=
  847.   D_h,D_v,D_w,D_x,D_y,D_z : integer;            {current \.{DVI} state values}
  848.   D_h_stack, D_v_stack, D_w_stack, D_x_stack, D_y_stack, D_z_stack:
  849.     array [0..max_stack+2] of integer; {pushed down values }
  850.   @!stack_ht: 0..max_stack;    {current stack depth}
  851.   just_pushed: boolean;
  852. @ @<Set up an empty page...@>=
  853.   D_w := 0 ; D_x := 0 ;
  854.   D_y := 0 ; D_z := 0 ;
  855.   stack_ht := 0 ;
  856.   rail_base := 0 ;
  857.   just_pushed := false ;
  858. @ Here is how \.{DVItype} manipulates the stack: The first |push| on a page
  859. fills the zeroth place on the stack and sets |stack_ht| = 1. So the used
  860. places are numbered |0..stack_ht- 1|. Now |push| and |pop| do the obvious
  861. things.
  862. @<Lowest...@>=
  863.   procedure push;
  864.   var x: real_number ;
  865.   begin if stack_ht=max_stack then
  866.     abort('Capacity exceeded (stack size=', max_stack:1,')')
  867.     else begin
  868.       D_h_stack[stack_ht]:=D_h; D_v_stack[stack_ht]:=D_v;
  869.       D_w_stack[stack_ht]:=D_w; D_x_stack[stack_ht]:=D_x;
  870.       D_y_stack[stack_ht]:=D_y; D_z_stack[stack_ht]:=D_z;
  871.       @<Some adjustments are needed here for rounding@>
  872.       incr(stack_ht); just_pushed := true ;
  873.     end;
  874.   end;
  875.     @.Fatal: Capacity exceeded...@>
  876. @# procedure pop;
  877.   begin if stack_ht=0 then warn('POP illegal at level zero')
  878.     else  begin
  879.       decr(stack_ht);
  880.       D_h:=D_h_stack[stack_ht]; D_v:=D_v_stack[stack_ht];
  881.       D_w:=D_w_stack[stack_ht]; D_x:=D_x_stack[stack_ht];
  882.       D_y:=D_y_stack[stack_ht]; D_z:=D_z_stack[stack_ht];
  883.       IM_h := IM_h_stack[stack_ht];IM_v := IM_v_stack[stack_ht];
  884.       @<Set |rail_base|@>
  885.     end;
  886.   end;
  887.     @.Error: POP illegal...@>
  888. @ This procedure gets called when we expect to read a new page. It looks for
  889. the next |BOP|; if it finds the postamble instead, it sets |count_pages < 0| as
  890. a signal.
  891. @d POST = 248
  892. @d NOP = 138
  893. @d BOP = 139
  894. @<Top level...@>=
  895.   procedure read_BOP;
  896.   var k: byte ; D_par:integer ;
  897.   begin
  898.     repeat k:= get_byte(dvi);
  899.       if (k>= 243)and(k <= 246 ) then {a |font_def|}
  900.       begin D_par:=get_integer(dvi) (k-242 ); define_font(D_par); k:=NOP;
  901.       end;
  902.     until k<>NOP;
  903.     if k=POST then
  904.     count_pages := -1
  905.     else if k<>BOP then bad_dvi('Byte is not BOP')
  906.       @.Fatal: Bad DVI: Byte not BOP@>
  907.     else begin
  908.       for k:=0 to 9 do counter[k]:= get_integer(dvi)(-4);
  909.       skip(dvi)(4);
  910.     end;
  911.   end;
  912. @ A \.{DVI}-reading program that reads the postamble first need not look at the
  913. preamble; but \.{Crudetype} reads the \.{DVI} file sequentially.
  914. @d PRE=247 {preamble}
  915. @<Read \.{DVI} preamble@>=
  916.   bbb:= get_byte(dvi); {fetch the first byte}
  917.   if bbb<>PRE then bad_dvi('First byte isn''t start of preamble!');
  918.     @.Fatal: Bad DVI: First byte...@>
  919.   bbb:= get_byte(dvi); {fetch the identification byte}
  920.   if bbb<>id_byte then
  921.   warn('Identification byte should be ',id_byte:1,', it is actually', bbb:1 );
  922.     @.Error: Identification...@>
  923.   @<Compute the conversion factors@>;
  924.   bbb:= get_byte(dvi); {fetch the length of the introductory comment}
  925.   if quiet then skip( dvi)( bbb)
  926.   else begin
  927.     for nnn := 1 to bbb do
  928.     display(zchr(get_byte(dvi)));
  929.     display_ln(' ');
  930.   end;
  931. @ The conversion factor |h_conv| is figured as follows: There are exactly
  932. |n/d| decimicrons per \.{DVI} unit and 254000 decimicrons per inch, and
  933. |h_resolution| |h_steps| per inch.
  934. @<Glob...@>=
  935.   dvi_factor, h_conv, v_conv, magnification : real_number;
  936.   D_l_margin, D_top_margin, nnn:integer; {general purpose register}
  937.   bbb: byte ;
  938. @ @<Compute the conversion factors@>=
  939.   dvi_factor := get_real(dvi)/254000.0 ;
  940.   {This converts \.{DVI} units to inches (on an ideal device) }
  941.   D_l_margin := round( l_margin/dvi_factor) ;
  942.   D_top_margin := round( top_margin/dvi_factor) ;
  943.   magnification :=  get_integer(dvi)(4) / 1000 ;
  944.   dvi_factor := dvi_factor * magnification ;
  945.   dvi_factor := dvi_factor * make_real( magnify/ 100.0) ;
  946.   {Extra magnification specified by user}
  947.   h_conv:= dvi_factor * h_resolution * h_fudge * make_real( h_mag/ 100.0) ; 
  948.   v_conv:= dvi_factor * v_resolution * v_fudge * make_real( v_mag/ 100.0) ; 
  949. @* Translating the device-independent file, 3: Setting a Rule.
  950. |D_p| is the height and |D_q| is the width. A rule has to be assembled from
  951. the available characters. First: is the rule to be set at all? Second: is it
  952. horizontal or vertical? (Because of the limited name lengths, we call them
  953. |Post| and |Rail|.) The test applied here is quite arbitrary.
  954. @<Medium...@>=
  955.   procedure set_rule;
  956.   var D_p,D_q: integer;
  957.   begin
  958.     D_p:=get_integer(dvi) (-4);
  959.     D_q:=get_integer(dvi)(-4);
  960.     if (D_p<=0)or(D_q<=0) then
  961.       {an invisible rule! Dont ask me why \TeX\ wants to do this}
  962.     else if (D_p*v_conv <= post_height/2)
  963.     then do_rail(D_p, D_q)
  964.     else do_post(D_p, D_q);
  965.   end;
  966. @ Setting a vertical rule is simple: we just fill all the space with the
  967. relevant character.
  968. @<Lowest...@>=
  969.   procedure do_post(D_rul_ht, D_rul_width: integer);
  970.   var vn, vi, hn, hi, post_v, rule_hp : integer;
  971.   rule_cod: code_object ;
  972.   begin
  973.     @<|Post| set sizes@>;
  974.     for vi := vn - 1 downto 0 do
  975.     begin
  976.       post_v := IM_v - vi * post_height ;
  977.       for hi := 1 to hn do
  978.       begin
  979.         rule_hp := IM_h + (hi - 1) * post_width ;
  980.         do_set_char(post_v, rule_hp, rule_cod);
  981.       end;
  982.     end;
  983.   end;
  984. @ Note that whereas \.{DVItype} rounds all sizes up, \.{Crudetype} rounds to
  985. nearest integer.  This seems more likely to work on a crude resolution. But we
  986. force the rounded size to be |>= 1| .
  987. @<|Post| set...@>=
  988.   round_IM_h ( 0);
  989.   hn := round(D_rul_width * h_conv / post_width );
  990.   vn := round(D_rul_ht * v_conv / post_height);
  991.   if hn <= 0 then hn := 1;
  992.   if vn <= 0 then vn := 1;
  993.   rule_cod := post_char;
  994. @ A horizontal rule is more complicated, as there is then a selection of
  995. characters. This matters if the printer has only a very coarse vertical
  996. positioning. For example, a line printer has only minus and underscore, but a
  997. VT-100 has 5 bars at different heights. |@!rail_types| should be set to the
  998. number of different horizontal bars that the printer can draw within one
  999. |v_step|. We measure the vertical position of a rule in |rail_steps|,
  1000. which are smaller than |v_steps| in the same ratio.
  1001. @<Glob...@>=
  1002.   rail_chars : packed array [1..rail_types] of code_object ;
  1003.     {Number from bottom of page up; so no. 1 might be an underscore}
  1004.   rail_base : integer ;
  1005.     {Position of bottom edge of a  rule in |rail_steps|}
  1006.   post_char : code_object ;
  1007. @ @<Const...@>=
  1008.   @<Rule setting constants@>
  1009.   {Printer-dependent, so they must go at the end of the file}
  1010. @ @<Lowest...@>=
  1011.   procedure do_rail(D_rul_ht, D_rul_width: integer);
  1012.   var vn, vi, hn, hi,
  1013.   rail_v,  {Current position in |rail_steps|}
  1014.   char_vp,  {Position in |v_steps| where a rule char will be set}
  1015.   rule_hp: integer;
  1016.   rule_cod: code_object ;
  1017.   char_i : 1..rail_types ; {indicates which character to be used}
  1018.   begin
  1019.     @<|Rail| set sizes@>
  1020.     for vi := vn-1 downto  0 do begin
  1021.       rail_v := rail_base - vi ;
  1022. @ Now to assign |char_i| and |char_vp|. The easiest way is to consider a simple
  1023. example. Suppose |rail_types = 5| and |rail_v = 50|. This addresses the
  1024. underscore at the bottom edge of a text character at |10 v_steps|.
  1025. So |char_i| wants to be 1 and |char_vp| 10. So...
  1026. @<Lowest...@>=
  1027.   char_vp := ((rail_v - 1) div rail_types ) + 1 ;
  1028.   char_i :=  rail_types - ((rail_v - 1) mod rail_types ) ;
  1029.   rule_cod := rail_chars [ char_i] ;
  1030.   for hi := 1 to hn do begin
  1031.     rule_hp := IM_h + (hi-1) * rail_width ;
  1032.     do_set_char(char_vp, rule_hp, rule_cod) ;
  1033.   end;
  1034. @ @<|Rail| set...@>=
  1035.   round_IM_h ( 0);
  1036.   hn := round(D_rul_width * h_conv/ rail_width);
  1037.   vn := round(D_rul_ht * v_conv * rail_types/ rail_height );
  1038.   if hn <= 0 then hn := 1;
  1039.   if vn <= 0 then vn := 1;
  1040. @ Now consider how to set |rail_base|. Horizontal rules are mostly used for
  1041. underlining text, and then they should be aligned with the underscore
  1042. character on the same line of text. So normally, we just do the following. The
  1043. exception occurs when the \.{DVI} file does an explicit vertical move.
  1044. @<Set |rail_base|@>=
  1045.   rail_base := IM_v * rail_types ;
  1046. @* Translating the device-independent file, 4: Changing and defining Fonts.
  1047. The following tables describe all the \TeX\ fonts that \.{Crudetype} currently
  1048. knows about.
  1049. @<Glob...@>=
  1050.   nf: D_font_ptr ;
  1051.     {The number of fonts so far defined. These will be numbered |0..nf-1| }
  1052.   @!font_num,         {external font numbers}
  1053.   @!font_space,       {boundary between ``small'' and ``large'' spaces}
  1054.   @!scheme,           {pointer into |codes|}
  1055.   @!first_ch,         {First character in the font}
  1056.   @!last_ch:          {and last}
  1057.     array [D_font_ptr] of integer;
  1058.   D_width: array[D_font_ptr, D_char_ptr ] of integer ;
  1059.     {character widths, as given in \.{TFM} file, should be in \.{DVI} units}
  1060.   @!D_check,     {the font checksum must be global for HPGF}
  1061.   thin_space, D_font, cur_scheme: integer ;     {The current values}
  1062. @ @<Type...@>=
  1063.   D_font_ptr = 0..max_D_fonts;
  1064.   D_char_ptr = 0..max_D_char;
  1065. @ The size of the tables can be altered at compile time to extend or reduce
  1066. \.{Crudetype}'s capacity.
  1067. @<Constants...@>=
  1068.   @!max_D_fonts=100; {maximum number of distinct fonts per \.{DVI} file}
  1069.   @!max_D_char = 255; {max. value of a \TeX\ character in a \TeX\ font}
  1070. @ Initially, all these tables are blank.
  1071. @<Set init...@>=
  1072.   nf:=0;
  1073.   for in_i := 0 to max_D_fonts do
  1074.   begin
  1075.     font_num[in_i ] := 0 ;
  1076.     scheme[in_i ] := 0 ;
  1077.     first_ch[in_i ] := 0 ;
  1078.     last_ch[in_i ] := 0 ;
  1079.     font_space[in_i]:= 0 ;
  1080.   end;
  1081. @ @<Set up an empty page...@>=
  1082.   thin_space := 0 ;
  1083.   D_font := nf  ;
  1084.   cur_scheme := 0 ;
  1085. @ @<Medium...@>=
  1086.   procedure change_font (D_new: integer);
  1087.   begin
  1088.     D_font := 0 ;
  1089.     font_num[nf]:=D_new;
  1090.     while( font_num[D_font]<>D_new) do incr(D_font);
  1091.     if D_font = nf then
  1092.     warn('Undefined font called for, number ', D_new:1 );
  1093.       @.Error: Undefined font@>
  1094.     cur_scheme := scheme[D_font] ;
  1095.     thin_space := font_space[D_font] ;
  1096.   end;
  1097. @ The following procedure is called whenever we read a |font_def| command from
  1098. the \.{DVI} file. In general, any error while defining a font causes a jump to
  1099. label |bad_font|, leaving the new font undefined. |good_font| is a hook for the 
  1100. HP change file.
  1101. @d bad_font = 9999
  1102. @d good_font = 9998
  1103. @d font_error(#) == begin
  1104.   string_show( font_name) ; display(' ---- ') ;
  1105.   warn( #);
  1106.   goto bad_font ;
  1107. @<Medium...@>=
  1108.   procedure define_font (D_new:integer );
  1109.   label bad_font , good_font ;
  1110.   var @<|font_def| vars@>
  1111.   begin
  1112.     @<Read the font parameters from the \.{DVI} file,
  1113.       calculate scaling factors@>;
  1114.     @<Try to load the new font, unless there are problems@>;
  1115.     good_font:
  1116.     @<Final checks; various mild errors which often are symptoms of bugs@>
  1117.     incr(nf) ; {the new font is officially present}
  1118.     bad_font: if font_ok then close_binary(tfm_file);
  1119.   end;
  1120. @ First we read the parameters from the \.{DVI} file. Whatever errors are
  1121. found, we must try to do this, or we lose place in the file.
  1122. @<|font_def| vars@>=
  1123.   scale_size, design_size, k, f : integer;
  1124.   dir_len,      {length of the area/directory spec}
  1125.   nam_len:byte; {length of the font name proper}
  1126.   font_mag: real_number;
  1127. @ @<Read the font parameters...@>=
  1128.   @!D_check := get_integer(dvi)(-4) ;
  1129.   scale_size:= get_integer(dvi)( -4) ;
  1130.   design_size:= get_integer(dvi)(-4) ;
  1131.   dir_len:= get_integer(dvi)(1) ;
  1132.   nam_len:= get_integer(dvi)(1) ;
  1133.   nam_len := nam_len + dir_len ;
  1134.   if nam_len = 0 then
  1135.   warn('Null font name! ')
  1136.     @.Error: Null font name@>
  1137.   else if nam_len >= max_string then
  1138.   font_error('Too-long font name! length =  ', nam_len:1 ) ;
  1139.     @.Error: Too-long font name@>
  1140.   tfm_name := blank ;
  1141.   for k:=1 to nam_len do begin
  1142.     tfm_name.data[k] := zchr(get_byte(dvi)) ;
  1143.   end;
  1144.   tfm_name.len := nam_len ;
  1145.   font_name := tfm_name ;
  1146. @ Next, check that the sizes are reasonable:
  1147. @<Read the font parameters...@>=
  1148.   if (scale_size<=0)or(scale_size>=@'1000000000) then
  1149.   font_error('Bad scale (',scale_size:1,')!')
  1150.     @.Error: Bad scale@>
  1151.   else if (design_size<=0)or(design_size>=@'1000000000) then
  1152.   font_error('Bad design size (',design_size:1,')!') ;
  1153.     @.Error: Bad design size@>
  1154.   font_mag := scale_size/design_size ;
  1155.   if (font_mag > 1000) or (font_mag < 0.001) then
  1156.   font_error('Way-out font magnification!!! ', font_mag) ;
  1157.     @.Error: Way-out font mag...@>
  1158.   if nf=max_D_fonts then
  1159.   abort('Capacity exceeded (max fonts=', max_D_fonts:1,')!');
  1160.     @.Fatal: Capacity exceeded...@>
  1161.   font_num[nf]:=D_new; f:=0;
  1162.   while( font_num[f]<>D_new) do incr(f);
  1163.   if f<nf then font_error('Font was already defined!');
  1164.     @.Error: Font already defined@>
  1165.   font_space[nf] := scale_size div 6 ; {a `thin space' }
  1166. @* Loading the font file.
  1167. See \.{TFTOPL} or \TeX 82 for details of the \.{TFM} file format. The
  1168. description given in \.{TUGboat} (Vol.2, no. 1) is apparently no longer
  1169. accurate. The only difference that I have seen is that all words of the font
  1170. header array after the first 2 are now apparently regarded as optional.
  1171. @.TFTOPL@> @.TeX82@> @.TUGboat@>
  1172. @<Try to load...@>=
  1173.   @<Open font file@>
  1174.   if not font_ok then
  1175.   font_error('TFM file can''t be opened!');
  1176.     @.Error: TFM file cant be opened@>
  1177.   @<Read past the header data, leave the file pointer just after the header@>
  1178.   @<Read the character-width indices@>
  1179.   @<Read the widths, copy them into the font array@>
  1180. @ @<|font_def| vars@>=
  1181.   @!font_ok: boolean ;
  1182.   @!TFM_check,
  1183.   @!lh, {length of the header data, in four-byte words}
  1184.   @!nw:integer; {number of words in the width table}
  1185. @ @<Read past the header...@>=
  1186.   skip(tfm)(2);                     lh:= get_integer(tfm)(2);
  1187.   first_ch[nf]:=get_integer(tfm)(2);   last_ch[nf]:=get_integer(tfm)(2);
  1188.   if (last_ch[nf]<first_ch[nf]) or (last_ch[nf] > max_D_char) then
  1189.   font_error(
  1190.     'Illegal values for first_char and/or last_char, first_char = ',
  1191.       first_ch[nf]:1 , ' last_char = ', last_ch[nf]:1 );
  1192.       @.Error: Illegal value@>
  1193.   nw:=get_integer(tfm)(2);
  1194.   if (nw=0)or(nw>256) then
  1195.   font_error('Illegal value for nw, nw= ', nw );
  1196.     @.Error: Illegal value@>
  1197.   skip(tfm)(14);
  1198.   TFM_check := get_integer(tfm)(-4);
  1199.   skip(tfm)(4);
  1200.   @<Get coding scheme and re-align file, then see if the printer knows it@>
  1201. @ The header contains |4*lh| bytes, of which 8 have been read so far. If it
  1202. conforms to the \.{TUGboat} format, then the next byte (|@!ck|, say) is the
  1203. number of bytes in the coding scheme name. So, first we must try to see if a
  1204. scheme is present; if so, then we will read |ck+1| bytes and chuck the rest.
  1205. If no coding scheme is present, we simply skip the rest of the header.
  1206. Internally, scheme names are represented by |var_string|s.
  1207. @<Get cod...@>=
  1208.   tfm_scheme := blank ;
  1209.   if lh < 2 then font_error( 'Header must have at least 2 words')
  1210.     @.Error: Header...@>
  1211.   else if lh = 2 then do_nothing
  1212.   else begin
  1213.     ck := get_byte(tfm);
  1214.     if ( ck >= 40 ) or ( ck > 4*lh - 9) then
  1215.     skip(tfm)(4*lh - 9)
  1216.     {there is something here, but not a coding scheme}
  1217.     else begin
  1218.       tfm_scheme.len := ck ;
  1219.       for k := 1 to ck do
  1220.       tfm_scheme.data[ k] := zchr(get_byte(tfm)) ;
  1221.       skip(tfm)(4*lh - ck - 9);
  1222.       upcase(tfm_scheme) ;
  1223.     end;
  1224.   end;
  1225. @ @<|font_def| vars@>=
  1226.   f_n , ck : byte ;
  1227.   try_name, tfm_scheme: var_string ; {coding scheme of current font}
  1228. @ Now we can start reading the character widths.
  1229. @<|font_def| vars@>=
  1230.   @!in_width:array[byte] of integer; {\.{TFM} width data in \.{DVI} units}
  1231.   @!wid_ptr: array[byte] of byte ; {pointers into |in_width|}
  1232.   b3,b2,b1,b0: byte;       {bytes from \.{TFM} file}
  1233.   @!alpha,@!beta, @!z :integer;
  1234. @ @<Read the character-width indices...@>=
  1235.   for k:=first_ch[nf] to last_ch[nf] do
  1236.   begin wid_ptr[k] := get_byte(tfm); skip(tfm)(3);
  1237.     if wid_ptr[k] > nw then font_error('Impossible width ' , wid_ptr[k]);
  1238.   end;
  1239.     @.Error: Impossible width@>
  1240. @ Here is the width computation. This code is copied from \.{DVItype}. See
  1241. that program for an explanation.
  1242. @<Read the font parameters...@>=
  1243.   z := scale_size ;
  1244.   alpha:=16*z; beta:=16;
  1245.   while( z>=@'40000000) do
  1246.   begin z:=z div 2; beta:=beta div 2;
  1247.   end;
  1248. @ @<Read the widths...@>=
  1249.   for k:=0 to nw-1 do
  1250.   begin
  1251.     b0 := get_byte(tfm); b1 := get_byte(tfm);
  1252.     b2 := get_byte(tfm); b3 := get_byte(tfm);
  1253.     in_width[k]:=
  1254.     (((((b3*z)div@'400)+(b2*z))div@'400)+(b1*z))div beta;
  1255.     if  b0 = 255 then in_width[k]:=in_width[k]-alpha
  1256.     else if b0 <> 0 then
  1257.     font_error('Out-of-bounds value for b0') ;
  1258.       @.Error: Out-of-bounds |b0|@>
  1259.   end ;
  1260. @ Rounding widths. This bit of \.{DVItype} is changed, because \.{Crudetype}
  1261. has to calculate rounded positions by a completely different method.
  1262. @<Read the widths...@>=
  1263.   if in_width[0]<>0 then font_error('First width should be zero ');
  1264.     @.Error: First width...@>
  1265.   for k:= first_ch[nf] to last_ch[nf] do
  1266.   D_width[nf, k] := in_width[ wid_ptr[k]] ;
  1267. @ Then there are various erroneous states that do not necessarily show that
  1268. the font is corrupt, but may indicate bugs in the program. In principle, a
  1269. character might have negative width, but I do not believe it.
  1270. @d bad_char = -32766  {Indicates an unprintable character}
  1271. @d foot == 50000000    {about a foot}
  1272. @<Final checks...@>=
  1273.   for k:= first_ch[nf] to last_ch[nf] do
  1274.   if (D_width[nf, k] < 0) or (D_width[nf, k] > foot) then begin
  1275.     warn('Way-out width = ', D_width[nf,k]:1,
  1276.       'DVI units, character number ', k:1 );
  1277.     codes[ scheme[nf], k].breadth := bad_char ;
  1278.   end;
  1279.   if (D_check<>0)and(TFM_check<>0)and(D_check<>TFM_check) then
  1280.   begin warn('Check sums do not agree!');
  1281.       @.Error: Check sums...@>
  1282.     display_ln('DVI check was: ', D_check, ' TFM check was: ', TFM_check);
  1283.     display('   ');
  1284.   end;
  1285.   font_mag := (font_mag -1) * 100.0 ;
  1286.   if not quiet then begin
  1287.     display_ln( ' ');
  1288.     string_show( font_name);
  1289.     display( ' --- loaded at ',scale_size:1,' DVI units');
  1290.     if abs(font_mag) > 1 then
  1291.       display(' ( magnified ', round(font_mag):1,'%)');
  1292.     display_ln(' ');
  1293.   end;
  1294.     @.loaded at ...@>
  1295.     @.magnified...@>@.Error: Way-out width@>
  1296. @* Coding schemes.
  1297. In this section we describe the mapping from \TeX\ fonts to the printer's
  1298. fonts. These are presumably much fewer because all characters on a crude
  1299. printer are the same size. The mapping is defined in an array called |codes|.
  1300. Each entry of this gives the printer's equivalent for a \TeX\ character.
  1301. @<Glob...@>=
  1302.   @!known_schemes: array[code_ptr] of var_string ;
  1303.   @!scheme_map: array [code_ptr] of code_ptr ;
  1304.   @!codes: array[code_ptr, D_char_ptr] of code_object;
  1305.   no_char: code_object ;
  1306.   scheme_top: code_ptr;
  1307. @ If |c| is a |code_object|, then |c.breadth| will usually be its printed
  1308. width in |h_steps|. |c.breadth = bad_char| indicates that the character is
  1309. unprintable. |bad_char| can be any large negative value. Other negative values
  1310. of |@!breadth| indicate other types of peculiar characters.
  1311. @d down_loaded = -32765
  1312. @<Types...@>=
  1313.   code_object = packed record
  1314.     breadth: i_word ;
  1315.     case boolean of
  1316.       true: (IM_font: byte ; IM_char: byte );
  1317.         {Printers font and character}
  1318.       false: (multi: i_word) ;
  1319.   end;
  1320.   code_ptr = 0..max_codes;
  1321.     {0 is a coding scheme the printer doesnt know about}
  1322. @ Initially, all these tables are blank.
  1323. @<Set init...@>=
  1324.   no_char.breadth := bad_char ;
  1325.   no_char.IM_font := 0 ;
  1326.   no_char.IM_char := 0 ;
  1327.   scheme_top := 0 ;
  1328.   for in_i := 0 to max_codes do begin
  1329.     known_schemes[in_i] := blank ;
  1330.     scheme_map[in_i] := 0 ;
  1331.     for in_j := 0 to max_D_char do begin
  1332.       codes[in_i, in_j] := no_char ;
  1333.     end;
  1334.   end;
  1335. @ This procedure sets a character. The character to be set is number |@!c_num|
  1336. in the current font. I have deleted the bit of \.{DVItype} that deals with
  1337. oriental fonts, as I dont believe that crude printers can support them.
  1338. @<Medium...@>=
  1339.   procedure set_character(c_num: integer );
  1340.   var cod: code_object;
  1341.   d_i : integer; {Used for downloading}
  1342.   begin
  1343.     if cur_scheme = 0 then
  1344.     else if (c_num < first_ch[D_font] ) or (c_num > last_ch[D_font] )
  1345.     then begin
  1346.       warn('Character ',c_num:1,' invalid in font number ',
  1347.         font_num[ D_font]:1 );
  1348.         @.Error: Character invalid...@>
  1349.     end
  1350.     else begin
  1351.       cod := codes[ cur_scheme, c_num];
  1352.       if cod.breadth <> bad_char then begin
  1353.         round_IM_h( c_num) ;
  1354.         if cod.breadth = down_loaded then
  1355.         @<Enter a download request for |cod| and adjust its |breadth|@> ;
  1356.         do_set_char(IM_v, IM_h, cod ) ;
  1357.         @<Do messy things to adjust the positions |D_h|, |IM_h|, etc@>;
  1358.       end;
  1359.     end;
  1360.   end;
  1361.   procedure do_set_char ;
  1362.   var k_i, k_k, temp_v, temp_h: i_word ;
  1363.   m_c: code_object ;
  1364.   k_ptr: 1..max_ligs;
  1365.   begin
  1366.     if cod.breadth >= 0 then begin
  1367.       @<Check the position@>
  1368.       @<Add the record to the page image@>
  1369.     end
  1370.     else if cod.breadth = bad_char then do_nothing
  1371.     else @<Set multi-character command@> ;
  1372.   end;
  1373. @ @<Forw...@>=
  1374.   procedure do_set_char(Set_v, Set_h: i_word; cod: code_object ); forward;
  1375. @ So when a font is read in, we try to find its coding scheme by comparing
  1376. the font with the list of |known_schemes|. If the printer is not absolutely
  1377. crude, then it might have italic or bold fonts. Then we might want a coding
  1378. scheme to correspond to a single \TeX\ font. But if the printer is
  1379. |fixed_width|, then all fonts of the same face are the same size. So first we
  1380. look at the actual font name and see if that matches any of the
  1381. |known_schemes|. If that fails, drop the font size digits off the end of the
  1382. name and try again. Then try again with the scheme given in the \.{TFM} file.
  1383. If the font matches |known_schemes[ s]| then |scheme_map[ s]| will point to
  1384. the relevant row of |codes|.
  1385. @<Read the font parameters...@>=
  1386.   try_name := tfm_name ;
  1387.   upcase( try_name) ;
  1388. @ @<Get cod...@>=
  1389.   f_n := name_search( try_name) ;
  1390.   if ( f_n = 0) then begin
  1391.     k := try_name.len ;
  1392.     while ((zord(try_name.data[k]) >= "0" ) and
  1393.     (zord(try_name.data[k]) <= "9" )) do begin
  1394.       try_name.data[ k] := ' ' ;
  1395.       decr( k) ;
  1396.     end;
  1397.     try_name.len := k ;
  1398.     f_n := name_search( try_name) ;
  1399.     if ( f_n = 0) then
  1400.     f_n := name_search( tfm_scheme) ;
  1401.   end;
  1402. @ If all these tries fail, then try if we can download the font. If that
  1403. fails print a warning and try using the TEX TEXT scheme -- this may have
  1404. some odd effects but it's worth a try.
  1405. @<Get cod...@>=
  1406.   if (f_n = 0) and can_dl_font then
  1407.   @<Download a whole font@>
  1408.   else if ( f_n = 0) then begin
  1409.     string_show( font_name) ;
  1410.     display_ln( ':');
  1411.     if ( tfm_scheme.len > 0) then
  1412.       warn( 'Unknown coding scheme, using TEX TEXT instead')
  1413.     else
  1414.       warn( 'No coding scheme, using TEX TEXT instead (examine NOSCHEME.ADD) ') ;
  1415.     scheme [ nf] := scheme_map[ 3] ;
  1416.     end
  1417.   else scheme[ nf] := scheme_map[ f_n];
  1418.     @.Error: Unknown coding scheme@>
  1419.     @.Error: No coding scheme@>
  1420. @ @<Lowest...@>=
  1421.   function name_search( ss: var_string): code_ptr;
  1422.   var i: code_ptr;
  1423.   begin i := scheme_top;
  1424.     while (( i > 0) and ( not equals( ss, known_schemes[ i]))) do decr( i) ;
  1425.     if ( i=0) then name_search := 0
  1426.     else name_search := i ;
  1427.   end;
  1428. @ This creates a scheme name. It gets used in the combination:
  1429. |be_string( 'QUEER SCHEME' ); set_scheme (123);|
  1430. @<Lowest...@>=
  1431.   procedure set_scheme( ix: code_ptr) ;
  1432.   begin
  1433.     incr( scheme_top) ;
  1434.     upcase( buffer);
  1435.     known_schemes[ scheme_top] := buffer ;
  1436.     scheme_map[ scheme_top] := ix ;
  1437.   end;
  1438. @* Multiple-character commands.
  1439. Several crude printers (e.g. daisy-wheels) have only a limited set of
  1440. characters, which cannot be extended. Sometimes you can generate more
  1441. characters by overstriking. \.{Crudetype} can be programmed to do this, by
  1442. placing suitable entries into a table called |ligatures|. The name is chosen
  1443. by analogy with the |lig_kern| programs in \.{TFM} files, but the data is
  1444. completely different. When one \TeX\ character maps onto several printer
  1445. characters, we call the image a `multi-character' command.
  1446. @<Const...@>=
  1447.   max_ligs = 10000 ;
  1448. @ @<Glob...@>=
  1449.   ligatures : array[1..max_ligs] of lig_thing;
  1450.   top_of_ligs: 0..max_ligs ; {highest used point in |ligatures|}
  1451. @ @<Types...@>=
  1452.   trio = 1..3 ;
  1453.   lig_thing = packed record
  1454.     case trio of
  1455.       1: (v_move: i_word ;
  1456.         h_move: i_word) ;
  1457.       2: (symbol: code_object) ;
  1458.       3: (num : i_word ;
  1459.         guard : i_word) ;
  1460.   end;
  1461. @ @<Set init...@>=
  1462.   top_of_ligs := 0;
  1463.   for in_i := 1 to max_ligs do ligatures[ in_i].symbol := no_char ;
  1464. @ The |code_object| addresses a multiple character when its |breadth| is
  1465. negative, and not one of the special classes defined above. It must then be
  1466. the |false| variant, and its |multi| field (which must be |>0|) points to the
  1467. corresponding entry in |ligatures|. Suppose that field is |c| . Then
  1468. |ligatures[c]| is the last entry of a string of items that defines the
  1469. replacement text of the |code|. It should be of the third variant; The |num|
  1470. field of this entry counts the number of characters that |code| expands into.
  1471. The |guard| field is an arbitrary impossible value called |sentry| to give a
  1472. check on the data in |ligatures| .
  1473. @d sentry = -32767
  1474. @<Set multi...@>=
  1475.   begin
  1476.     if (cod.multi <= 0) or (cod.multi > top_of_ligs) then
  1477.     warn('Illegal value of char in multi-character command')
  1478.       @.Error: Illegal value@>
  1479.     else begin
  1480.       k_ptr := cod.multi ;
  1481.       if (ligatures[k_ptr].guard <> sentry) then
  1482.       warn('Sentry not found in Kerns ' ) ;
  1483.         @.Error: Sentry ...@>
  1484.       k_i := ligatures[k_ptr].num ;
  1485.       k_ptr := k_ptr - 2*k_i ;
  1486.       if (k_i <= 0) or (k_ptr < 0 ) then
  1487.       warn('Illegal value of k_i in multi-character command');
  1488.         @.Error: illegal value@>
  1489.       for  k_k := 1 to k_i do
  1490.       @<Get that character and write it@>;
  1491.     end;
  1492.   end
  1493. @ Each character of a multi-character command needs 2 entries in |ligatures|.
  1494. The first defines the position, the second defines the character. |v_move| and
  1495. |h_move| are relative to the current (rounded) position |Set_v, Set_h| and use
  1496. the same units. A multi-character command can call another one recursively.
  1497. @<Get that character ...@>=
  1498.   begin
  1499.     temp_v := Set_v + ligatures[k_ptr].v_move ;
  1500.     temp_h := Set_h + ligatures[k_ptr].h_move ;
  1501.     incr(k_ptr);
  1502.     m_c := ligatures[k_ptr].symbol ;
  1503.     do_set_char(temp_v, temp_h, m_c ) ;
  1504.     incr(k_ptr);
  1505.   end;
  1506. @* Getting data into the |codes| array.
  1507. This is clearly a very long and error-prone job, so the next procedures are put
  1508. in to reduce this. First suppose that: in the \TeX\ coding scheme with number
  1509. |s|, a run of |length| characters starting from |start| maps onto a run of
  1510. consecutive characters in printer font |PR_font|, starting at |PR_first|. This
  1511. procedure will enter the whole run at one go.
  1512. @<Lowest...@>=
  1513.   procedure alphabet
  1514.   (start, length: byte; s: code_ptr ; PR_font, PR_first : byte );
  1515.   var i:integer; ccc:code_object;
  1516.   begin @<Check alphabet data@>;
  1517.     ccc.IM_font := PR_font ;
  1518.     ccc.breadth := char_width ;
  1519.     for i := 0 to length-1 do begin
  1520.       ccc.IM_char := PR_first +i;
  1521.       codes[s, start+i] := ccc ;
  1522.     end; end;
  1523. @ @<Check alph...@>=
  1524.   if (s < 1) then abort('Alphabet: scheme < 1 ')
  1525.   else if (s > max_codes) then abort('Alphabet: scheme too large')
  1526.   else if (PR_first < 0) then abort('Alphabet: negative first')
  1527.   else if (start < 0) then abort('Alphabet:  negative start')
  1528.   else if (length < 0) then abort('Alphabet: negative length')
  1529.   else if (start + length -1 > max_D_char) then abort('Alphabet: overflow')
  1530.     @.Fatal: Alphabet...@>
  1531. @ Clearly, |alphabet| will only cover a very small part of the problem.  The
  1532. next procedure enters data into a subset of the |codes| array corresponding to
  1533. a single row of a \TeX\ font. In the standard font tables, row number |m| is
  1534. the subrange |8*m..8*m+7| of a font. We hope that when the calls of |row| are
  1535. written out in a program, the result will be (just about) legible, whereas a
  1536. flood of statements like  \begintt
  1537.          codes[i,j].IM_font := 121; \endtt
  1538. is certainly not legible. 
  1539. Because \PASCAL\ has no decent string handling, we cannot call |row|
  1540. in any sensible way. It must be used in combination:
  1541. | be_string( 'row-spec') ; row( scheme, row_num, first_font);|
  1542. The |@!row_spec| is a character string that specifies what characters
  1543. are to go into the row; the |be_string| is needed to turn the
  1544. |row_spec| into a |var_string|.  |@!scheme | is the number assigned to
  1545. the \TeX\ coding scheme within the program. |@!row_num | is the number
  1546. of the row in that scheme (starting from 0). |@!first_font| is the
  1547. initial printer font. The diagnostics of |row| are known to be poor,
  1548. but they are really intended for the installer rather than the
  1549. end-user; so I have not tried to improve them.
  1550. @<Top...@>=
  1551.   procedure row ( scheme, row_num: integer; first_font: i_word );
  1552.   label exit ;
  1553.   var n :integer;  codd: code_object;
  1554.   begin
  1555.     row_font := first_font ;
  1556.     finger := 0 ;
  1557.     for n := 8*row_num to  8*row_num + 7 do begin
  1558.       row_char (0 ,codd);
  1559.       if ( codd.breadth = bad_char) then do_nothing
  1560.       else codes[ scheme, n ] := codd ;
  1561.     end;
  1562.     exit:
  1563.   end;
  1564. @ @<Glob...@>=
  1565.   row_font: i_word; {printer font being addressed during the |row| procedure}
  1566. @ The overall format of the |row_spec| is a set of 8 character specifiers
  1567. separated by one or more spaces. The procedure |row_char| reads one character
  1568. specifier from the |row_string|, and constructs the specified |code_object|.
  1569. Logically, |row_char| should be a function and return that |code_object| as
  1570. its value. \PASCAL\ does not permit this. So we assemble the result in the
  1571. variable parameter |value|.
  1572. @<Medium...@>=
  1573.   procedure row_char(context: integer; var value: code_object);
  1574.   label exit ;
  1575.   const @<|Row_char| constants@>
  1576.   var @<Row locals@>
  1577.   begin
  1578.     value.breadth := char_width ; {default}
  1579.     value.IM_font := row_font ;    {default font}
  1580.     get_char ;
  1581.     if ( context=0) and ( ch <> " " )  and ( ch <> "Z" ) then
  1582.     string_warn('Character specifiers must start with at least one space') ;
  1583.     while (inside and space) do get_char;
  1584.     if not inside then string_warn ( 'Fallen off row') ;
  1585.     @<Escape sequences in the |row_spec|@>
  1586.     else value.IM_char := ch ;
  1587.     exit:
  1588.   end;
  1589.     @.Error: Character spec...@>@.Error: Fallen off row@>
  1590. @ There are several escape sequences that need to go into the |rowstring|.
  1591. Since all the PLAIN.TEX coding schemes (except the math extension one) have
  1592. the upper case Roman characters in their ASCII positions, these characters
  1593. will surely be inserted into |codes| by the |alphabet| procedure. So they are
  1594. available as flag characters. But the brackets are also used as flags, as they
  1595. are so much more perspicuous than anything else. Here is a list of the
  1596. characters currently used as escapes: \begintt
  1597. A C D E F K L M N S Q U W Z \endtt
  1598. This list should be updated if other escapes are added .
  1599. @^Escape sequences@>@.ASCII@>
  1600. @ Some characters, called `bad', have most undesirable effects when used in
  1601. \.{WEB} strings. So the following upper-case letters stand for them. The
  1602. actual characters may not be used, so they generate errors.
  1603. @<Esc...@>=
  1604.   if ( ch = "A") then value.IM_char := 64   {at sign}
  1605.   else if ( ch = "S") then value.IM_char := 32   {a space}
  1606.   else if ( ch = "Q") then value.IM_char := 39   { a single quote char}
  1607.   else if ( ch = "W") then value.IM_char := 34   { a double quote char }
  1608.   else if ( ch = "E") then value.IM_char := 127  { a delete char }
  1609.   else if (ch = "'") or (ch = """") or (ch = "@@") or (ch = 127)
  1610.     then string_warn( 'Bad character---Rejected' )
  1611.   else if (ch = " ")
  1612.     then string_warn('Space found out of context')
  1613.       @.Error: Bad character@>@.Error: Space found...@>
  1614. @ Then the  `Z' escape is provided to generate a do-nothing  code. This would
  1615. be used if a previous call (say, of |alphabet|) had left a row partly
  1616. incorrect. Then you might issue a call of |row| to change that row. Typing `Z'
  1617. at the positions occupied by correct characters would leave them alone.
  1618. @<Esc...@>=
  1619.   else if ( ch = "Z") then value.breadth :=  bad_char
  1620. @ Since many letters and brackets are used as escapes, the `L' escape is
  1621. needed to enable them to be used Literally. `LL' generates `L'.
  1622. @<Esc...@>=
  1623.   else if ( ch = "L") then begin
  1624.     get_char; value.IM_char := ch ; end
  1625. @ In order to address printer characters in the range 0..32, where ASCII has
  1626. no graphics, here is a Control escape. This simply reads the next character
  1627. from the |row_spec| and reduces it modulo 32. It is best to use the lower case
  1628. alphabet (the range 95..126) as this avoids all the `bad' characters (and
  1629. their escapes). So control-A should be typed `Ca' , not `CA' .
  1630. Then the Meta escape addresses meta-characters, i.e. those in the range
  1631. 128..255. We cannot just read a character and add 128, as we might want to
  1632. Mutate the ASCII controls, or the `bad' characters. So `M' must read a complete
  1633. |code_object| (respecting the escapes given above) and add 128 to its |IM_char|
  1634. field. So we must say `MS' for `meta-space' = 160 , and  `MLS' for `meta-S'
  1635. = 211 .
  1636. @.ASCII@>
  1637. @d M_con == 250
  1638.     {Context while reading a Meta character}
  1639. @<Esc...@>=
  1640.   else if ( ch = "C") then begin
  1641.     get_char; value.IM_char := ch mod 32 ; end
  1642.   else if ( context >= M_con) then value.IM_char := ch
  1643.     {During a Meta, forbid any of the later escapes}
  1644.   else if ( ch = "M") then
  1645.   begin
  1646.     row_char(M_con , value ) ;
  1647.     value.IM_char := value.IM_char + 128 ;
  1648.   end
  1649. @ A |narrow| character is one with zero width. To generate one, precede it
  1650. with an `N' . To mark a character  as down-loadable, precede it with  `D'. A
  1651. character cannot be both narrow and down-loadable.
  1652. @d N_con == 230
  1653.     {Context while reading a Narrow or |down_loaded| character}
  1654. @<Esc...@>=
  1655.   else if ((ch = "N" ) or (ch = "D" )) and (context >= N_con) then
  1656.   string_warn('Narrow or Down escape out of context')
  1657.     @.Error: Narrow escape...@>
  1658.   else if ( ch = "N") then
  1659.   begin
  1660.     row_char(N_con, value ) ;
  1661.     value.breadth := 0 ;
  1662.   end
  1663.   else if ( ch = "D") then
  1664.   begin
  1665.     row_char(N_con, value ) ;
  1666.     value.breadth := down_loaded ;
  1667.   end
  1668. @ Changing printer fonts in the middle of a |row| is done by inserting an `F'
  1669. character, followed by an integer. This is the printer font to be used, from
  1670. now on till the next `F' . Note that the initial font was passed as the 3rd
  1671. parameter to |row|.
  1672. @<Esc...@>=
  1673.   else if ( ch = "F") then begin
  1674.     row_font := s_to_i ( 0, true);
  1675.     if ( context = 0) then row_char(1, value)
  1676.     else  row_char(context, value);
  1677.   end
  1678. @* Assembling a multi-character in |row|.
  1679. Now we come to the difficult part, which is assembling a multiple-character
  1680. command into the |ligatures| array. For this purpose, we use brackets. Curly
  1681. brackets mean that the characters inside are to be overstruck, square brackets
  1682. mean they are to be typed horizontally, and angle brackets mean that they are
  1683. to be typed vertically above each other. Finally the `U' escape (which must
  1684. come immediately after a |<| ) means to raise the (logical) cursor before
  1685. starting the vertical list.
  1686.     Warning!! I use the numerical (\.{ASCII}) values of these chars
  1687. @^System dependencies@>@.ASCII@>
  1688. @<|Row_char| const...@>=
  1689.   o_bra = "{" ;   o_ket = "}" ;
  1690.   h_bra = "[" ;   h_ket = "]" ;
  1691.   v_bra = "<" ;   v_ket = ">" ;
  1692.     {`o' means overstrike, `h' means horizontal, and `v' vertical}
  1693. @ So if we want to generate a Macsyma style summation sign, which looks like
  1694. this: \begintt
  1695. .                   ====
  1696. .                   \
  1697. .                    >
  1698. .                   /
  1699. .                   ====
  1700. \endtt
  1701. we have to insert the following mess into the |row_spec| string: \begintt
  1702.             <S[====]\[SL>]/[====]>
  1703. \endtt
  1704. The `S' is needed to get correct vertical alignment. The  `L'  is needed to
  1705. prevent the following |>| being taken as a |ket|. See the end of this file
  1706. for examples.
  1707. @ In order to keep some control over all these escape sequences, I have made a
  1708. special rule of syntax. The escape sequences in |row_char| may only be nested
  1709. in a definite order. That order is: (bad characters or Control or Literal)
  1710. inside Meta inside (Narrow or Down-loadable) inside Font inside |o-list|s
  1711. inside |h_list|s inside |v_list|s. The parameter |context| keeps track of
  1712. this. The innermost constructions have the highest values of |context|. If
  1713. these rules are broken the user should get an error message.
  1714. @<Esc...@>=
  1715.   else if (ch = o_bra) or (ch = h_bra) or (ch = v_bra)
  1716.   then begin
  1717.     if ( context >=  ch) then
  1718.     string_warn('Illegal nesting of brackets in row_spec');
  1719.       @.Error: Illegal nesting@>
  1720.     @<Assemble characters into |lig_buff| until we read the matching |ket|@>;
  1721.     @<Copy |lig_buff| into |ligatures| and return a pointer to it@>;
  1722.   end
  1723. @ |hoister| and |ender| are arbitrarily selected impossible classes for a
  1724. character, indicating respectively that a |v_list| has to be raised one
  1725. |char_ht| or that a |ket| has been read.
  1726. @d hoister = -32764
  1727. @d ender   = -32763
  1728. @<Assemble char...@>=
  1729.   for i := 1 to max_buf do lig_buff[ i].symbol := no_char ;
  1730.   buf_len := 0; delta_h := 0; delta_v := 0;
  1731.   bra := ch ;
  1732.   repeat
  1733.     row_char( bra, row_cod ) ;
  1734.     @<Do suitable action if |row_cod| is peculiar@>
  1735.     else begin
  1736.         incr(buf_len);
  1737.         lig_buff[buf_len].v_move := delta_v ;
  1738.         lig_buff[buf_len].h_move := delta_h ;
  1739.         incr(buf_len);
  1740.         lig_buff[buf_len].symbol := row_cod ;
  1741.         if ( bra = v_bra) then delta_v := delta_v + char_ht;
  1742.         if ( ( bra = h_bra) or ( batch_view and ( bra = o_bra)))
  1743.         then delta_h := delta_h + char_width ;
  1744.       end;
  1745.   until ( row_cod.breadth = ender) or not inside;
  1746. @ @<Row loc...@>=
  1747.   lig_buff: array[1..max_buf] of lig_thing ;
  1748.   buf_num: 0..max_buf ;
  1749.     {Number of characters (or multi-characters) in current list}
  1750.   buf_len: 0..max_buf ;
  1751.     {Number of used locations in |lig_buff|: should be |2*buf_num|}
  1752.   delta_h, delta_v: i_word;
  1753.   ch, bra: byte;
  1754.   i: integer;
  1755.   row_cod: code_object ;
  1756. @ @<Const...@>=
  1757.   max_buf = 201;
  1758. @ @<Do suitable action...@>=
  1759.   if ( row_cod.breadth = hoister) then delta_v := delta_v - char_ht
  1760.   else if ( row_cod.breadth = ender) then
  1761.   else if ( buf_len + 3 > max_buf) then
  1762.     abort('Overflowed lig_buff array')
  1763.     @.Fatal: Overflowed |lig_buff|@>
  1764. @ @<Esc...@>=
  1765.   else if (ch = "U" ) and (context = v_bra) then value.breadth := hoister
  1766.   else if (ch = "U" ) then
  1767.   string_warn('U escape out of context')
  1768.     @.Error: U escape...@>
  1769.   else if ((ch = o_ket) or (ch = h_ket) or (ch = v_ket)) and (context = ch-2)
  1770.   then value.breadth := ender
  1771.   else if (ch = o_ket) or (ch = h_ket) or (ch = v_ket)
  1772.   then string_warn('Mismatching brackets ')
  1773.     @.Error: Mismatching brackets@>
  1774. @ Yet another escape is the |kern| escape. If the printer has reasonable
  1775. positioning resolution, we may want to move the parts of a multi-character
  1776. about to make them fit together better. So a |kern| takes an integer parameter
  1777. and moves the next component of the current list by that many |steps| against
  1778. the current direction. The reason for going back is that one can easily move
  1779. forwards by setting a blank space.
  1780. @d h_kern = -32762
  1781. @d v_kern = -32761
  1782. @<Esc...@>=
  1783.   else if (ch = "K") and (context = h_bra) then value.breadth := h_kern
  1784.   else if (ch = "K") and (context = v_bra) then value.breadth := v_kern
  1785.   else if (ch = "K")
  1786.   then string_warn('Kern escape out of context' )
  1787.     @.Error: Kern escape@>
  1788. @ @<Do suitable action...@>=
  1789.   else if (  row_cod.breadth = h_kern)
  1790.   then delta_h := delta_h - s_to_i ( 0, true)
  1791.   else if (  row_cod.breadth = v_kern)
  1792.   then delta_v := delta_v - s_to_i ( 0, true)
  1793. @ @<Copy...@>=
  1794.   buf_num := 0 ;
  1795.   if ( buf_len = 0) then value.breadth :=  bad_char
  1796.   else if ( top_of_ligs + buf_len + 1 >= max_ligs) then
  1797.   abort ('Ligature array overflowed, must recompile with larger array')
  1798.     @.Fatal: Ligature array overflowed@>
  1799.   else begin
  1800.     for i := 1 to buf_len do
  1801.     ligatures[ top_of_ligs + i ] := lig_buff[i] ;
  1802.     top_of_ligs := top_of_ligs + buf_len + 1 ;
  1803.     buf_num := buf_len div 2 ;
  1804.     ligatures[top_of_ligs].num:= buf_num ;
  1805.     ligatures[top_of_ligs].guard := sentry ;
  1806.     value.multi := top_of_ligs ;
  1807.     value.breadth := -20000 ;
  1808.     {Provisional: a nonsense value to make sure the correct value does get
  1809.       inserted later}
  1810.   end;
  1811. @* Character strings.
  1812. In this section I have tried to provide some tolerable string-handling
  1813. facilities in despite of the restrictions of \PASCAL. This does not seem to
  1814. belong in any particular place in the program, but in view of the horrible
  1815. gruesome things that will happen in the next section, it seemed a good idea to
  1816. give some light relief. That is why this section is inserted here.
  1817. The |var_string| type is principally used for file names and to send command
  1818. sequences to the printer. Logically, these procedures should all be functions
  1819. and return the results, but stupid \PASCAL\ does not allow this. It would of
  1820. course be much cleaner to use the VMS |varying| type, but that would make the
  1821. program non-portable.
  1822. @<Const...@>= max_string = 100 ; {a guess, of course}
  1823. @ @<Types...@>=
  1824.   s_ptr = 0..max_string ;
  1825.   fix_string = packed array[ 1..max_string ] of char ;
  1826.   var_string= packed record
  1827.     len: byte;
  1828.     data: fix_string ;
  1829.   end ;
  1830. @ |@!blank| is used for initialising strings. It should not be altered
  1831. anywhere but here. |buffer| is used for terminal input, etc.
  1832. @<Set |blank|@>=
  1833.   blank.len := 0 ;
  1834.   for in_i := 1 to max_string do
  1835.   blank.data[in_i] := pad_char ;
  1836. @ @<Glob...@>=
  1837.   ch: byte ;
  1838.   hack: fix_string ;
  1839.   buffer, blank: var_string ;
  1840.   thumb, finger: s_ptr ;
  1841. @ This procedure converts an explicit quoted string into a |var_string|. 
  1842. @<Lowest...@>=
  1843.   procedure set_string ;
  1844.     var i, j: byte ;
  1845.   begin
  1846.     result := blank ;
  1847.     set_j_to_length ;
  1848.     if padded then begin
  1849.         while (( j > first ) and ( ss[j] = pad_char)) do decr(j) ;
  1850.         if ( j = first ) and ( ss[j] = pad_char) then decr(j) ;
  1851.       end;
  1852.     j := j - first + 1 ;
  1853.     if ( j >max_string) then
  1854.       warn( 'String too long')
  1855.     else begin
  1856.         result.len := j ;
  1857.         for i := 1 to j do
  1858.           result.data[i] := ss[ first + i - 1 ] ;
  1859.       end;
  1860.   end;
  1861.     @.Error: String too long@>
  1862. @ @<Forw...@>=
  1863.   procedure substring( var result: var_string;
  1864.     ss:var_string; start, length: integer); forward;
  1865.   procedure append( var head: var_string; tail: var_string); forward;
  1866.   procedure add_char(var s: var_string; c: char) ; forward;
  1867.   function equals( s, t: var_string): boolean; forward ;
  1868.   procedure splice (
  1869.     var out: var_string; source: var_string; nn: integer); forward ;
  1870.   procedure print_string( var f: text; ss: var_string; control: char); forward;
  1871.   procedure int_string(
  1872.     var result: var_string; n: integer; cc: char ) ; forward ;
  1873.   procedure int_base(
  1874.     var result: var_string; nn: integer; s: byte) ; forward ;
  1875.   function s_search( s: var_string;
  1876.     target: char; go: integer): s_ptr; forward;
  1877.   procedure upcase( var ss: var_string); forward;
  1878.   function s_to_i( default: integer; insist: boolean): integer;  forward ;
  1879.   procedure get_name( var val: var_string); forward;
  1880. @ @<Low...@>=
  1881.   procedure substring ;
  1882.     var i : s_ptr ;
  1883.   begin
  1884.     result := blank ;
  1885.     if ((start <= 0) or ( length < 0) or ( start + length > ss.len+1  )) then
  1886.       warn( 'Impossible substring')
  1887.     else begin
  1888.         result.len := length ;
  1889.         for i := 1 to length do
  1890.           result.data[ i] := ss.data[ i - 1 + start] ;
  1891.       end;
  1892.   end;
  1893.     @.Error: Impossible substring@>
  1894.   procedure append ;
  1895.     var k: integer;
  1896.   begin
  1897.     if (head.len + tail.len > max_string )
  1898.     then warn('string too long')
  1899.     else begin
  1900.         for k := 1 to tail.len do
  1901.           head.data[ k + head.len] := tail.data[ k] ;
  1902.         head.len := head.len + tail.len ;
  1903.       end;
  1904.   end;
  1905.     @.Error: string too long@>
  1906.   procedure add_char ;
  1907.   begin
  1908.     if s.len >= max_string then warn('string too long')
  1909.     else begin
  1910.         incr(s.len) ;
  1911.         s.data[s.len] := c ;
  1912.       end;
  1913.   end;
  1914.     @.Error: String too long@>
  1915.   function equals ;
  1916.   begin if ( s.len <> t.len ) then equals := false
  1917.     else equals := ( s.data = t.data) ;
  1918.   end;
  1919. @ Printer commands usually have the format (prefix)(parameter)(suffix). In
  1920. order to generate these in a clean fashion, the next procedure |splice| puts
  1921. the value into the marked position in the |source| string. The position is
  1922. marked by the |@!amp_and| character, and the next character |cc| indicates what
  1923. type of number is to be inserted. If there is no |amp_and|, the number is
  1924. ignored.
  1925. @<Lowest...@>=
  1926.   procedure print_command( 
  1927.       pattern: var_string; val: integer; control: char) ;
  1928.     var ss: var_string;
  1929.   begin
  1930.     splice( ss, pattern, val);
  1931.     print_string( printfile, ss, control) ;
  1932.   end;
  1933. @ Also, printer commands usually contain unprintable characters. In my
  1934. experience, these are always ASCII controls, i.e. in the range |0..31|.  So
  1935. |print_string| has a |control| escape character passed to it. Normally this is
  1936. |@!ctrl_mark|, which is |'^'|.  This replaces the next character by its control
  1937. character, (i.e. reduced modulo 32) except: if |control| is a space, nothing is
  1938. changed; and 2 consecutive |control|s  get replaced by one.
  1939. @<Lowest...@>=
  1940.   procedure print_string ;
  1941.     var i: s_ptr ; is_con: boolean ; cc: char ;
  1942.   begin
  1943.     is_con := false ;
  1944.     for i := 1 to ss.len  do
  1945.       begin
  1946.         cc := ss.data[ i] ;
  1947.         if is_con then begin
  1948.             if ( cc <> control) then
  1949.               cc := zchr( zord( cc) mod 32) ;
  1950.             is_con := false ;
  1951.             write( f, cc) ;
  1952.           end
  1953.         else if  (( cc = control) and ( control <> ' ' )) then
  1954.           is_con := true
  1955.         else write( f, cc) ;
  1956.       end;
  1957.   end;
  1958. @ This procedure inserts a parameter.
  1959. @<Lowest...@>=
  1960.   procedure splice ;
  1961.     var i: integer; cc: char ;
  1962.     tail: var_string;
  1963.   begin
  1964.     i := s_search( source, amp_and, 1);
  1965.     if ( i=0) then out := source
  1966.     else begin
  1967.         substring( out, source, 1, i-1);
  1968.         substring( tail, source, i+2, source.len - i - 1 );
  1969.         cc := source.data[ i+1] ;
  1970.         int_string( out, nn, cc);
  1971.         append( out, tail);
  1972.       end;
  1973.   end;
  1974. @ |cc| (as above) is one character, and may have the values
  1975. `B'(yte), `D'(ecimal), `H'(exadecimal), `O'(ctal), or `W' (a 16-bit signed
  1976. word, in twos-complement notation).
  1977. @<Lowest...@>=
  1978.   procedure int_string ;
  1979.   var nn: integer ;
  1980.   begin if (cc = 'O') then int_base( result, n, 8)
  1981.     else if (cc = 'H') then int_base( result, n, 16)
  1982.     else if (cc = 'D') then int_base( result, n, 10 )
  1983.     else if (cc = 'B') and (n >= 0) and (n <= 255)
  1984.     then add_char( result, zchr(n))
  1985.     else if (cc = 'B') then warn('Out-of-range byte')
  1986.     else if (cc='W') then begin
  1987.       if (n>= 0) and (n <= 32767) then begin
  1988.         add_char( result, zchr(n div 256));
  1989.         add_char( result, zchr(n mod 256));
  1990.       end
  1991.       else if (n<0 ) and (n> -32768) then begin
  1992.         nn := n + 65536 ;
  1993.         add_char( result, zchr(nn div 256));
  1994.         add_char( result, zchr(nn mod 256));
  1995.       end
  1996.       else warn('Out-of-range word') ;
  1997.     end
  1998.     @<Hook for weird parameter types@>
  1999.     else warn('Int_string  called with illegal type') ;
  2000.   end;
  2001.     @.Error: Out-of-range...@> @.Error: Int_string called...@>
  2002.   procedure int_base ;
  2003.   var nh : integer ;
  2004.   begin
  2005.     nh := nn ;
  2006.     if nh < 0 then begin add_char( result, '-'); nh := - nh ; end ;
  2007.     if nh >= s then begin
  2008.       int_base( result, nh div s, s) ;
  2009.       nh := nh mod s ;
  2010.     end ;
  2011.     if nh >= 10 then add_char( result, zchr(nh - 10 + "A" ))
  2012.     else add_char( result, zchr(nh + "0"  )) ;
  2013.   end;
  2014. @ @<Hook for weird ...@>=
  2015. @ The next macros and routines are for parsing strings. In these, the string
  2016. being parsed is called |buffer|. |finger| points to the next character that
  2017. is due to be read, and |ch| is the ordinal of this character. When an error
  2018. is found, |thumb| should point to the first wrong character.
  2019. @d get_char == begin incr( finger); ch := zord( buffer.data[finger]) ; end
  2020. @d inside == ( finger <= buffer.len)
  2021. @d digit == (( ch >= "0") and (ch <= "9" ))
  2022. @d lo == (( ch >= "a") and (ch <= "z" ))
  2023. @d cap == (( ch >= "A") and (ch <= "Z" ))
  2024. @d equal_sign == ( ch = "=")
  2025. @d is_prefix == ( ch = prefix )
  2026. @d space == ( ch = " " )
  2027. @d letter == lo or cap
  2028. @d up(#) == # + "A" - "a"
  2029. @d small(#) == # - "A" + "a"
  2030. @d string_warn(#) == begin
  2031.   warn(#);
  2032.   string_show( buffer) ;
  2033.   display_ln(' ');
  2034.   display_ln('^' : thumb) ;
  2035.   return;
  2036. @<Lowest...@>=
  2037.   procedure upcase ;
  2038.   var i: s_ptr; ch: byte;
  2039.   begin
  2040.     for i := 1 to ss.len do begin
  2041.       ch:= zord( ss.data[i]) ;
  2042.       if lo then
  2043.       ss.data[i] := zchr( up( ch )) ;
  2044.     end;
  2045.   end;
  2046. @ This function tries to read an integer from the |buffer|, starting at
  2047. position |finger+1|. The integer may be signed and may be preceded by spaces.
  2048. If there is no integer, return the |default|; error if |insist| is true.
  2049. V3: A rather crude addition. In Unix the |prefix| is `{\tt -}', and the 
  2050. {\tt /i} key wants an optional integer arg. So the system will tend to confuse 
  2051. a prefix `{\tt -}' with a negative arg. So in suitable cases |s_to_i| will 
  2052. backspace.
  2053. @d prev_char == if (finger > 1) then
  2054.   begin decr( finger); ch := zord( buffer.data[finger]) ; end 
  2055. @<Low...@>=
  2056.   function s_to_i ;
  2057.     label exit ;
  2058.     var value, sig : integer;
  2059.   begin
  2060.     value := default ;
  2061.     sig := 1;
  2062.     while (inside and space) do get_char;
  2063.     if ( ch = "+" ) then get_char
  2064.     else if ( ch = "-" ) then begin
  2065.         sig := -1; get_char; end;
  2066.     thumb := finger ;
  2067.     if inside and digit then begin
  2068.         value := 0 ;
  2069.         while (inside and digit) do begin
  2070.             value := value * 10 + ( zord( ch) - "0") ; get_char; end;
  2071.         value := value * sig ;
  2072.       end
  2073.     else if insist then string_warn( 'Integer expected ') 
  2074.     else if ((sig = -1) and (prefix = "-")) then prev_char ;
  2075.     exit: s_to_i := value;
  2076.   end;
  2077.     @.Error: Integer expected@>
  2078. @ When parsing a command string, this procedure tries to read a filename
  2079. starting from |finger|. Various things might go wrong: |finger| might be
  2080. pointing at nothing; a qualifier; an integer argument; or a valid name
  2081. mis-spelled. I dont see any plausible way to distinguish these; so I continue
  2082. regardless.
  2083. @<Low...@>=
  2084.   procedure get_name ;
  2085.   begin
  2086.     while (inside and (space or equal_sign)) do get_char;
  2087.     thumb := finger ;
  2088.     while (inside and not (space or is_prefix)) do get_char;
  2089.     substring( val, buffer, thumb, finger - thumb ) ;
  2090.   end;
  2091. @ This function searches for character |target| in a string, starting from
  2092. position |go|. Return 0 if not found. If |go < 0| search backwards from |-go|.
  2093. Error if |go = 0| or if |go| is out-of-bounds.
  2094. @<Lowest...@>=
  2095.   function s_search ;
  2096.   label exit ;
  2097.   var nn, gg: integer;
  2098.   begin
  2099.     gg := abs( go);
  2100.     nn := 0 ;
  2101.     if (gg > s.len ) or (go =0 ) then warn ( 'Impossible search')
  2102.     else begin
  2103.       if ( go > 0 ) then begin
  2104.         for nn := gg to s.len do if ( s.data[ nn] = target) then return ; end
  2105.       else begin
  2106.         for nn := gg downto 1 do if ( s.data[ nn] = target) then return ; end ;
  2107.       nn := 0 ;
  2108.     end;
  2109.     exit: s_search := nn ;
  2110.   end;
  2111.     @.Error: Impossible search@>
  2112. @ This is the Standard part of the procedure that parses a command line. When
  2113. a |key| requires a value, some people will probably be used to typing {\tt
  2114. key = value} or perhaps {\tt key <space> value} so |parse_command| must allow
  2115. these constructions.
  2116. @<Med...@>=
  2117.   procedure parse_command;
  2118.   label exit ;
  2119.   var key: byte;
  2120.   begin
  2121.     buffer := command ; finger := 0 ; get_char;
  2122.     while inside do begin
  2123.       while (inside and space) do get_char ;
  2124.       if inside and ( ch = prefix) then begin
  2125.         get_char ; thumb := finger ;
  2126.         if lo then ch := up( ch) ; key := ch ; get_char ;
  2127.         while (inside and ( space or equal_sign)) do get_char ;
  2128.         @<If the |key| is valid, do the corresponding command@>
  2129.         else string_warn( 'Unknown command ') ;
  2130.       end
  2131.       else if inside then begin
  2132.         if ( dvi_name.len > 0) then begin
  2133.           warn( 'Two filenames. Previous name will be ignored, it was:' ) ;
  2134.           string_show( dvi_name) ;
  2135.           display_ln(' ') ;
  2136.         end;
  2137.         get_name( dvi_name) ;
  2138.       end;
  2139.       exit: end;
  2140.   end;
  2141.     @.Error: Unknown command@>
  2142.     @.Error: Two filenames...@>
  2143. @* Translating the device-independent file, 5: Movements.
  2144. This section considers the problem of deciding where each character has to be
  2145. printed on the printer's page. This is by far and away the most difficult (and
  2146. unsatisfactory) part of \.{Crudetype}. The current version is not a properly
  2147. designed algorithm; it is merely a bodge, obtained by a lot of trial and
  2148. error. It does seem to give tolerable results on \.{WEB} files, lineprinter,
  2149. and VMS. The main variables are: |@!D_h| is `\TeX's cursor'. It gives the
  2150. `exact' horizontal position (in \.{DVI} units) generated by \.{DVI} commands.
  2151. This is always updated exactly as in \.{DVItype}. |@!IM_h| is the `page
  2152. image's cursor'. It marks the position (in |h_steps|) where the next character
  2153. will be set.
  2154. The procedure |round_IM_h| is called immediately before we set a character or
  2155. a rule. We have to take account of all the movements that occurred since the
  2156. last previous character was set.
  2157. @<Forw...@>= procedure round_IM_h( code: byte); forward ;
  2158. @ @<Lowest...@>=
  2159.   procedure round_IM_h ;
  2160.   var
  2161.   s_top, diff, n, m,
  2162.   delta, new_IM_h, rounded_h : integer ;
  2163.   begin
  2164.     @<Find the new position |new_IM_h|@>
  2165.     IM_h := new_IM_h ;
  2166.   end;
  2167. @ The obvious method is to multiply |D_h| by a factor |h_conv| and round to
  2168. nearest integer. This gives extremely bad results, because the characters in
  2169. \TeX\ fonts vary enormously in width, while many crude printers have
  2170. |fixed_width| characters. If |h_conv| is too large, then you get spaces in the
  2171. middle of words. If |h_conv| is too small, then successive characters in a
  2172. word get printed on top of each other. With an intermediate value of |h_conv|,
  2173. you get both effects at once; in other words, the characters in \TeX\ fonts
  2174. vary so much in width that the `too large' and `too small' values of |h_conv|
  2175. overlap. In this situation, a great deal of jiggery-pokery is needed to get a
  2176. tolerable result (sometimes! I have not been able to make this code work in
  2177. general.)
  2178.   For a start, here is the algorithm used in \.{DVItype}. |D_h_right| and
  2179. |IM_h_right| give the latest value of |D_h| and |IM_h| after the latest
  2180. previous character or rule was set. If the horizontal motion is small, like a
  2181. kern, |IM_h| changes by rounding the kern; but when the motion is large,
  2182. |IM_h| changes by rounding the true position |D_h| so that accumulated
  2183. rounding errors disappear. Also, we insist that the total amount of drift is
  2184. bounded.
  2185. @d h_step_round(#) == round(h_conv*( # ))
  2186. @d max_drift == 2
  2187. @<Find the new position |new_IM_h|@>=
  2188.   rounded_h := h_step_round(D_h + D_l_margin) ;
  2189.   delta := D_h - D_h_right ;
  2190.   if (delta > thin_space) or (delta <= -4*thin_space) then
  2191.   new_IM_h := rounded_h
  2192.   else new_IM_h := IM_h_right + h_step_round(delta);
  2193.   if not fixed_width then begin
  2194.     if (new_IM_h > rounded_h + max_drift)
  2195.       then new_IM_h := rounded_h + max_drift
  2196.     else if (new_IM_h < rounded_h - max_drift)
  2197.       then new_IM_h := rounded_h - max_drift ;
  2198.   end else
  2199. @ Calculating |IM_h| on a |fixed_width| printer is very hairy. If we are not
  2200. careful, then the spaces between words will sometimes get rounded to 0. Since
  2201. we round `large' movements by rounding |D_h|, the space may even get rounded to
  2202. a negative value, if there was previously a lot of drift. So we must re-round
  2203. |new_IM_h|. The next idea is that whenever \TeX\ moves right by an amount that
  2204. seems large enough to be a space between words, we force |IM_h| to increase.
  2205. @<Find the new position |new_IM_h|@>=
  2206.   if (delta > thin_space) and (new_IM_h < IM_h_right + gap_width)
  2207.   @<Except in some special cases@>
  2208.   then new_IM_h := IM_h_right + gap_width
  2209.   else if (delta > - 2*thin_space) then begin
  2210.     if (new_IM_h < IM_h_right)
  2211.     then new_IM_h := IM_h_right; end
  2212.   else
  2213. @ Here are two little fudges which improve the result. First, when \TeX\ puts
  2214. out a thin space, it sometimes is a bit too small to be recognised as such. So
  2215. we reduce the |font_space| when a font is defined.
  2216. @<Read the font parameters...@>=
  2217.   font_space[nf] := round(font_space[nf] * 0.99 ) ;
  2218. @ The next fudge is needed to handle tables of contents. \TeX\ prints these by
  2219. putting out long streams of dots with small spaces in between. If these spaces
  2220. all get expanded to a whole character width, the right hand columns get thrown
  2221. right off the paper. So dont expand if the next character is a stop or comma.
  2222. @<Except in some special cases@>=
  2223.   and not ( ( ( code = ".") or ( code = ",") ) and
  2224.     ( ( cur_scheme > 0)  and ( cur_scheme <= max_plain )))
  2225. @ When these alternatives fail, we have lost contact between |D_h| and
  2226. |D_h_right|. This happens when \TeX\ makes a large backspace; in fact \TeX\
  2227. seems nearly always to do large backspaces by |pop| rather than an explicit
  2228. move left. \TeX\ often expresses boxes by a sequence like this:
  2229. \centerline{\tt{
  2230. PUS\markarrow{H}  Move right ------------>
  2231. \markarrow{[}set characters] \markarrow{P}OP   }}
  2232. followed by zero or more |push|es, then by a move either to one of the
  2233. positions marked by the arrows, or close by. I try to deal with this by
  2234. dropping markers at each of the arrowed positions. The markers are labelled
  2235. |D_h_right|, etc, and each marker has a corresponding value of |IM_h|
  2236. attached.
  2237. @<Glob...@>=
  2238.   D_h_left, IM_h_left, D_h_mid, IM_h_mid, D_h_right, IM_h_right,  {the markers}
  2239.   IM_h, IM_v, D_dis, IM_dis, H_shunt: integer;
  2240.   IM_h_stack, IM_v_stack:
  2241.     array [0..max_stack+2] of integer; {pushed down values }
  2242. @ Suppose that we are about to set a character, and |D_h-D_h_right| is large
  2243. and negative. Then we compare the current value of |D_h| with all the markers.
  2244. Let |m| be the closest of these, and |mm| the corresponding rounded value.
  2245. Then we re-round |new_IM_h| to force it to lie on the `correct' side of |mm|.
  2246. This seems to work fairly often, but it does sometimes slip. First put the
  2247. markers on top of the stack...
  2248. @<Find the new position |new_IM_h|@>=
  2249.   begin s_top := stack_ht ;
  2250.     D_h_stack[s_top] := D_h_left;
  2251.     IM_h_stack[s_top] := IM_h_left;
  2252.     incr(s_top) ;
  2253.     D_h_stack[s_top] := D_h_mid;
  2254.     IM_h_stack[s_top] := IM_h_mid;
  2255.     incr(s_top) ;
  2256.     D_h_stack[s_top] := D_h_right;
  2257.     IM_h_stack[s_top] := IM_h_right;
  2258. @ ...then look for the stacked value closest to |D_h|...
  2259. @<Find the new position |new_IM_h|@>=
  2260.   m := s_top ;
  2261.   for n := s_top downto 1 do begin
  2262.     diff := D_h - D_h_stack[n] ;
  2263.     if abs(diff) <= abs(delta) then
  2264.     begin m := n ; delta := diff; end ;
  2265.   end;
  2266. @ ...then adjust |new_IM_h| by reference to this point on the stack.
  2267. @<Find the new position |new_IM_h|@>=
  2268.   if (delta > thin_space ) and ( new_IM_h < IM_h_stack[m] + gap_width) then
  2269.   new_IM_h := IM_h_stack[m] + gap_width
  2270.   else if (delta < -thin_space )
  2271.   and ( new_IM_h > IM_h_stack[m] - gap_width)
  2272.   then new_IM_h := IM_h_stack[m] - gap_width
  2273.   else if abs(delta) <= thin_space then new_IM_h := IM_h_stack[m];
  2274. @ We must assign values to these markers. When we start a page, all the
  2275. markers that were left over from the previous page are irrelevant. So we reset
  2276. them. This is a good place to consider margins. The standard arrangement given
  2277. in the \TeX book (Chapter 23) is that \.{DVI} point $(0,0)$ is about an inch
  2278. in from the top and left edges of the paper. But a negative {\tt \BS hoffset}
  2279. allows \.{DVI} to address points with negative coordinates, which should still
  2280. be on the paper. It seems that the least messy way to implement this is by
  2281. adding |l_margin| to |IM_h|, whenever this is set to an absolute value.
  2282. Least messy, but wrong. It seems that first: we must alter not the scaled units
  2283. but the \.{DVI} units before scaling. Also to overcome the confusion of a
  2284. negative {\tt \BS hoffset} we must start each page by pushing a position
  2285. corresponding to the edge of the paper. |@!l_margin| is the assumed margin in
  2286. inches. |@!D_l_margin| is the equivalent in \.{DVI} units.
  2287. @^\TeX book@>@^Margins@>
  2288. @<Set up an empty page...@>=
  2289.   D_h := - D_l_margin ; D_v := - D_top_margin ;
  2290.   IM_h := 0 ; IM_v := 0 ;
  2291.   D_h_left := - D_l_margin ; IM_h_left := 0 ;
  2292.   D_h_mid :=  - D_l_margin; IM_h_mid := 0 ;
  2293.   D_h_right := - D_l_margin ; IM_h_right := 0 ;
  2294.   push ;
  2295.   D_h := 0 ; D_v := 0 ;
  2296.   H_shunt := 100000000 ;  {any absurd value}
  2297. @ Unfortunately the various fudge factors we have introduced expand this
  2298. inch of margin to about 3 inches; this will probably push some printed
  2299. characters off the right hand edge of the paper. So we must shift all
  2300. printed chars left by an amount |@!H_shunt|, defined as $-3 +$ the least value
  2301. of |IM_h| for any character set on the page. 
  2302. @<Add the record...@>=
  2303.   if H_shunt > (Set_h - 3)
  2304.   then H_shunt :=  (Set_h - 3) ;
  2305. @ So now we consider the three arrows in turn. The left hand arrow will be
  2306. marked by |@!D_h_left|. It records the latest horizontal position to be
  2307. |push|ed. There might have been a |pop| since then, so it is not necessarily
  2308. the value at the top of the stack. If we just record |IM_h| whenever we
  2309. |push|, that would give a wrong value whenever there was a sequence
  2310. |push..move_right..push|. So we must rectify the pushed value of |IM_h|.
  2311. @<Some adjustments...@>=
  2312.   IM_h_stack[stack_ht]:=IM_h;
  2313.   IM_v_stack[stack_ht]:=IM_v;
  2314.   if just_pushed and (stack_ht > 0) then begin
  2315.       x := h_conv*(D_h_stack[stack_ht] - D_h_stack[stack_ht - 1] );
  2316.       if abs(x) > 1.5 {a guess!} then
  2317.         IM_h_stack[stack_ht] := IM_h_stack[stack_ht] + round(x) ;
  2318.     end;
  2319.   D_h_left := D_h ;
  2320.   IM_h_left := IM_h_stack[stack_ht] ;
  2321. @ The centre arrow will be marked by |@!D_h_mid|. This is defined as the value
  2322. of |D_h| just before setting the first character after the latest |push|.
  2323. @<Find the new position |new_IM_h|@>=
  2324.   if just_pushed then begin
  2325.     D_h_mid := D_h ;
  2326.     IM_h_mid := new_IM_h;
  2327.     just_pushed := false;
  2328.   end;
  2329. @ The right hand arrow is marked by |@!D_h_right|. At any time, this is
  2330. defined as the right hand edge of the latest previous character (or rule) that
  2331. has just been set. This equals |D_h + D_dis|, where |D_dis| is the \TeX\ width
  2332. of the character. Usually there will follow a |move_right| that updates |D_h|,
  2333. but |D_h_right| must be updated even if there is no |move_right|. Now
  2334. |@!IM_h_right| must be aligned with the right hand edge of the printed
  2335. representation of the character. The idea is that this will usually be the
  2336. exact place where the next character has to be set. We hope that all the
  2337. characters in each word will be correctly placed next to one another and the
  2338. accumulated drift will appear in spaces between the words. So whenever a
  2339. character is set, we must assign values to |D_dis| and |IM_dis|. The character
  2340. is described by |cod|, and its printed width is written into its |breadth|
  2341. field; but if it is a multiple character, then the |breadth| is the negative
  2342. of the width.
  2343. @<Do messy things...@>=
  2344.   D_dis := D_width[D_font, c_num] ;
  2345.   if cod.breadth = bad_char then IM_dis := 0
  2346.   else IM_dis := abs(cod.breadth) ;
  2347.   @<Set |rail_base|@>
  2348.   D_h_right := D_h + D_dis ;
  2349.   IM_h_right := IM_h + IM_dis ;
  2350. @ So the procedure |row| must give the |breadth| field the right value when
  2351. assembling a |multi| character. Recall that that character can be either an
  2352. |o_list| or an |h_list| or a |v_list|, and |bra| tells us which it is. An
  2353. |o_list| is assumed to have a width of one |char_width| and the width of a
  2354. |v_list| is the width of its widest component. The width of a |h_list| gets
  2355. accumulated in |delta_h| as the list is assembled.
  2356. @<Copy |lig_buff|...@>=
  2357.   if ( bra = o_bra) then print_width := char_width
  2358.   else if ( bra = h_bra) then print_width := delta_h
  2359.   else begin
  2360.     print_width := char_width ;
  2361.     for i := 1 to buf_num do
  2362.     with lig_buff[2*i].symbol do
  2363.     if (print_width < -breadth ) and (breadth > -30000 )
  2364.     then print_width := -breadth  ;
  2365.   end;
  2366.   value.breadth := - print_width ;
  2367. @ @<Row locals...@>=
  2368.   print_width: integer ;
  2369. @ We must do the same thing when setting a rule.
  2370. @<|Post| set...@>=
  2371.   D_dis := D_rul_width ;
  2372.   IM_dis := hn * post_width ;
  2373.   D_h_right := D_h + D_dis ;
  2374.   IM_h_right := IM_h + IM_dis ;
  2375. @ @<|Rail| set...@>=
  2376.   D_dis := D_rul_width ;
  2377.   IM_dis := hn * rail_width ;
  2378.   D_h_right := D_h + D_dis ;
  2379.   IM_h_right := IM_h + IM_dis ;
  2380. @ \.{DVItype} handles vertical motion in the same sort of way as horizontal.
  2381. @d v_step_round(#) == round(v_conv*( # ))
  2382. @<Medium...@>=
  2383.   procedure move_down(ddd: integer);
  2384.   var new_IM_v , delta : integer;
  2385.   begin
  2386.     D_v:=D_v+ddd;
  2387.     delta := v_step_round(ddd) ;
  2388.     @<Find a vertical position |new_IM_v|@>
  2389.   end;
  2390. @ @<Find a vert...@>=
  2391.   if delta >= big_drop then begin
  2392.     new_IM_v := v_step_round(D_v + D_top_margin) ;
  2393.     if (new_IM_v < IM_v + big_drop) then
  2394.     IM_v := IM_v + big_drop
  2395.     else IM_v := new_IM_v ;
  2396.     rail_base := IM_v * rail_types ;
  2397.   end
  2398.   else if (delta <= -big_drop) then begin
  2399.     new_IM_v := v_step_round(D_v + D_top_margin) ;
  2400.     if (new_IM_v > IM_v - big_drop) then
  2401.     IM_v := IM_v - big_drop
  2402.     else IM_v := new_IM_v ;
  2403.     rail_base := IM_v * rail_types ;
  2404.   end else
  2405. @ The above calculation fails for small motions. Because \TeX\ expects
  2406. subscripts to be about half the size of the main line, it drops them by only a
  2407. small amount; with a crude printer, this small amount gets rounded to zero. If
  2408. the move is smaller than |@!tiny_drop| \.{DVI} units, we ignore it. If not,
  2409. then we force the new value of |IM_v| to be different from the old.
  2410. @<Find a vert...@>=
  2411.   begin
  2412.     IM_v := IM_v + delta ;
  2413.     rail_base := rail_base +  v_step_round(ddd * rail_types) ;
  2414.     if (ddd >  tiny_drop) and ( delta = 0) then IM_v := IM_v + 1
  2415.     else if (ddd < -tiny_drop) and ( delta = 0) then IM_v := IM_v - 1
  2416.     else rail_base := IM_v * rail_types ;
  2417.   end;
  2418. @ The next bit is put in to help catch bugs. Sometimes the \.{DVI} file really
  2419. does try to address an absurd position; for example, I contrived to make \TeX\
  2420. generate a {\tt \BS hbox} that was 9000 points wide. More often, nonsense
  2421. positions are created by bugs. If we do nothing about this, \.{Crudetype} will
  2422. probably crash with an arithmetic error, which is unacceptable. So any
  2423. character falling outside the limits |h_min..h_max| and |v_min..v_max| will
  2424. generate an error report.
  2425. @<Check the position@>=
  2426.   if (Set_h < h_min) 
  2427.   then begin
  2428.       warn('Out of bounds --left') ;
  2429.       Set_h := 0 ;
  2430.       {Chuck the character somewhere, hopefully out of the way}
  2431.     end else if ( Set_h > h_max )
  2432.   then begin
  2433.       warn('Out of bounds --right') ;
  2434.       Set_h := h_max ;
  2435.       {Chuck the character somewhere, hopefully out of the way}
  2436.     end ;
  2437.   if (Set_v < v_min) or ( Set_v > v_max )
  2438.   then begin
  2439.       warn('Out of bounds -- vertical ') ;
  2440.       Set_v := v_max ;
  2441.     end;
  2442.     @.Error: Out of bounds@>
  2443. @ @<Set init...@>=
  2444.   h_max := h_resolution * 100 ;
  2445.   v_max := v_resolution * 100 ;
  2446.   h_min := 0 ;
  2447.   v_min := 0 ;
  2448. @ Note that since the position fields of a |page_record| are subranges,
  2449. |h_max| etc. must be of the same type.
  2450. @<Glob...@>= h_max, v_max , h_min, v_min : i_word ;
  2451. @* Sorting the page.
  2452. Once we have assembled the complete page image, we must sort it. The method
  2453. used here is a merge sort based on the country dance called Grand March.
  2454. @<Sort the page@>=
  2455.   @<The dancers form a long line up the middle of the hall and march
  2456.     up towards the Presence@>
  2457.   repeat
  2458.     @<At the top they split, and alternate groups go to the left and right and
  2459.       march down the sides@>
  2460.     @<At the bottom of the hall, each group coming from the right hand side
  2461.       merges with a group from the left side, and they go up again@>
  2462.   until sorted;
  2463. @ Since the data being sorted is of unpredictable size and sequentially
  2464. processed, it logically ought to be a |file|. But this turned out to make the
  2465. program spectacularly slow. So I use linked lists instead--- a sacrifice of
  2466. logic to economy. But I continue to use file-like language.
  2467. @d send_one_set_to( #)==
  2468.   copy_from( mid ) ( # )
  2469. @<At the top...@>=
  2470.   L_reset( mid) ;
  2471.   L_rewrite( left) ;
  2472.   L_rewrite( right) ;
  2473.   repeat
  2474.     send_one_set_to( left) ;
  2475.     if not L_eof( mid) then
  2476.     send_one_set_to( right) ;
  2477.   until L_eof( mid);
  2478. @ Eventually everybody comes together in one enormous set and the dance is
  2479. finished. The easiest way to detect this is to let it go round one more time.
  2480. Then the left side of the hall will be full and the right hand side empty.
  2481.  @<At the bottom...@>=
  2482.   L_rewrite( mid) ;
  2483.   L_reset( left) ;
  2484.   L_reset( right) ;
  2485.   sorted := L_eof( right) ;
  2486.   if sorted then
  2487.     cur_pge_ptr := son( next( left))
  2488.   else repeat
  2489.     if L_eof( right) then copy_from( left)  ( mid)
  2490.     else if L_eof( left) then  copy_from( right)  ( mid)
  2491.     else @<Merge one group from each side@>
  2492.   until L_eof( left) and L_eof( right) ;
  2493. @ The natural way to assemble the page image is to throw everything into one
  2494. huge list, then start sorting. But the code for merging two simple lists was
  2495. horribly complicated. (The code given here merely merges two runs.) So the
  2496. page image is a list of lists (another sacrifice of logic to economy). Each
  2497. top-level entry has a |son|, which points to a sub-list. This is a sorted
  2498. subset (a ``run'') of the data. One advantage of the list-of-lists structure
  2499. is that we can take advantage of the fact that \TeX\ output is very ``runny''.
  2500. I found that this made \.{Crudetype} run at least 3 times faster than before.
  2501. @d Add_run == new_tail( mid_ptr) ; son( mid_ptr) := run_ptr ;
  2502. @<Merge one group...@>=
  2503.   begin
  2504.     L_rewrite( run) ;
  2505.     L_run_ptr := son( left_ptr) ;
  2506.     R_run_ptr := son( right_ptr) ;
  2507.     repeat
  2508.       if @<The person on the left is more eligible@>
  2509.       then copy_from( L_run) ( run)
  2510.       else copy_from( R_run) ( run) ;
  2511.     until L_eof( R_run) and  L_eof( L_run) ;
  2512.     step_wipe( left_ptr) ;
  2513.     step_wipe( right_ptr) ;
  2514.     L_reset( run) ;
  2515.     Add_run ;
  2516.   end;
  2517. @ So while the page image is being assembled, it must be divided into runs.
  2518. @<Add the record...@>=
  2519.   begin
  2520.     if out_of_sequence then begin {create a new run}
  2521.       L_reset( run) ;
  2522.       Add_run ;
  2523.       L_rewrite( run) ;
  2524.     end;
  2525.     new_tail( run_ptr ) ;
  2526.     with image( run_ptr) do begin {write the data into it}
  2527.       hpos := Set_h ; Old_h := Set_h ;
  2528.       vpos := Set_v ; Old_v := Set_v ;
  2529.       symbol := cod ;
  2530.     end;
  2531.     incr(page_size) ;
  2532.     if page_size >= page_max then abort(
  2533.       'Overflowed page: either a bug, or recompile with larger page_max' ) ;
  2534.   end
  2535.     @.Fatal: Overflowed page@>
  2536. @ Once the lists are all assembled, we must |reset| them before sorting.
  2537. @<The dancers...@>=
  2538.   sorted := false;
  2539.   L_reset( run) ;
  2540.   Add_run ;
  2541. @ Now we must specify the desired order!! That is: increasing |vpos| and
  2542. |hpos|, |vpos| is more significant.
  2543. @d out_of_sequence ==
  2544.   ( ( Old_v > Set_v) or ( ( Old_v = Set_v) and ( Old_h > Set_h)))
  2545. @<The person on the left is more eligible@>=
  2546.   ( ( image( L_run_ptr).vpos < image( R_run_ptr).vpos) or
  2547.     ( ( image( L_run_ptr).vpos = image( R_run_ptr).vpos)
  2548.       and ( image( L_run_ptr).hpos <= image( R_run_ptr).hpos)))
  2549. @ And here we get it all started. Since |garbage| wipes out everything in the
  2550. |pool| array above |zzz|, the following code effectively makes |mid..run|
  2551. permanent.
  2552. @<Set init...@>=
  2553.   first_cell ;
  2554.   make_new( mid );
  2555.   make_new( left );
  2556.   make_new( right );
  2557.   make_new( run );
  2558.   make_new( zzz );
  2559.   image(zzz).vpos := max_half;
  2560.   next(zzz) := zzz ;
  2561.   mid_ptr := zzz ;
  2562.   run_ptr := zzz ;
  2563. @ @<Set up an empty page...@>=
  2564.   garbage ;
  2565.   L_rewrite( mid) ;
  2566.   L_rewrite( run) ;
  2567.   page_size := 0 ;
  2568.   Old_v := -max_half ;
  2569. @ @<Glob...@>=
  2570.   zzz, cell, tempp, cur_pge_ptr,
  2571.   mid, mid_ptr, run, run_ptr,
  2572.   left, left_ptr, L_run_ptr ,
  2573.   right, right_ptr, R_run_ptr : link;
  2574.   page_size: page_i ;
  2575.   Old_v, Old_h : i_word ;
  2576.   sorted: boolean ;
  2577.   declare_pool
  2578. @ Now we must define lots of machinery for handling lists. We could represent
  2579. a list by either a big array or dynamic storage. Neither is ideal, because an
  2580. array is bound to be either too big or too small; and some \PASCAL s
  2581. apparently do not implement pointers. So I have expressed everything in terms
  2582. of certain macros, defined in the system dependent part of the program. In
  2583. theory, you can switch \.{Crudetype} from array to heap merely by redefining
  2584. these as follows:
  2585. \begintt
  2586.     define image(#) == #^
  2587.     define create == new(cell)
  2588.     define first_cell == do_nothing
  2589.     define link_type == ^page_record
  2590.     define wipe_out(#) == dispose(#) ; { release data piecemeal}
  2591.     define garbage == do_nothing
  2592.     define declare_pool == do_nothing
  2593. \endtt
  2594. Both array and heap seem to work in VMS. I prefer to use an array because in
  2595. VMS, there seems to be no shortage of store, and an array is easier to debug.
  2596. Assuming these lowest-level macros, here is some machinery for handling lists.
  2597. We must deallocate cells after use. When using arrays, the |garbage| command
  2598. does it all in one go. Pointers must be |dispose|d one at a time, and the
  2599. obvious time is just after the data was used.
  2600. @d next(#) == image(#).prox
  2601. @d advance(#) == # := next(#)
  2602. @d make_new( #) == create; # := cell ;
  2603. @d new_tail( #) ==
  2604.   create; next( #) := cell; # := cell ;
  2605. @d step_wipe( #) ==
  2606.    tempp := # ; advance( #) ; wipe_out( tempp)
  2607. @ Suppose |L| is a list; then the actual variable |L| points to a permanently-
  2608. allocated cell which in turn points to the head of the list. |L_ptr| points to
  2609. the active end. After the list has been assembled, we first mark the tail, by
  2610. attaching a special element called |zzz|. Then we move the |L_ptr| round to
  2611. the head. |copy_from| must be used in the combination
  2612. {\tt copy\_from(A)(B)}. It copies one element from the head of |A| to the
  2613. tail of |B|.
  2614. @d L_rewrite( #) ==
  2615.   #@&ptr := # ; next( #) := zzz
  2616. @d L_reset( #) ==
  2617.   next ( # @& ptr) := zzz ; #@&ptr := next( #)
  2618. @d L_eof( #) ==
  2619.   ( # @& ptr = zzz)
  2620. @d copy_end( #) ==
  2621.   next( #@&ptr) := tempp ; advance( #@&ptr) ; end
  2622. @d copy_from( #)==
  2623. begin
  2624.   tempp := #@&ptr ;
  2625.   advance( #@&ptr ) ;
  2626.   copy_end
  2627. @ Each top-level entry has the |false| type below; the |prox| field points to
  2628. the next top-level entry and the |down| field to a sub-list.
  2629. @d son(#) == image(#).down
  2630. @<Types...@>=
  2631.   page_i = 0..page_max ;
  2632.   link = link_type ;
  2633.   page_record = packed record
  2634.     prox: link ;
  2635.     case boolean of
  2636.       true: ( hpos : i_word;
  2637.         vpos: i_word;
  2638.         symbol: code_object ) ;
  2639.       false: ( down : link) ;
  2640.     end;
  2641. @* Processing a page of output.
  2642. The output of \.{Crudetype} is done by the procedure |Send_page|, which takes
  2643. the page and translates it for the printer. We shall process it a `line' at a
  2644. time, meaning all |page_records| with the same |vpos|. Initially |PR_font|
  2645. gets an impossible value so as to force an explicit |set_PR_font|.
  2646. @<Top level...@>=
  2647.   procedure Send_page;
  2648.     var line: link ;
  2649.   begin
  2650.     PR_font := sentry;
  2651.     PR_h := 0;
  2652.     PR_v := 0;
  2653.     while (not L_eof( cur_pge))
  2654.     do begin
  2655.         line := read_line ;
  2656.         do_line( line, 0);
  2657.       end ;
  2658.   end;
  2659. @ The function |read_line| runs along the page image until the vertical
  2660. position changes. It returns a pointer to a sublist which is the next line on
  2661. the page. As side effects, it advances |cur_pge_ptr| to the first record of
  2662. the next line, and updates |PR_v_next|.
  2663. @<Medium...@>=
  2664.   function read_line : link ;
  2665.   var head, tail: link ; size: integer;
  2666.   begin
  2667.     head := cur_pge_ptr ;
  2668.     size := 0 ;
  2669.     PR_v_next := image(cur_pge_ptr).vpos ;
  2670.     repeat
  2671.       tail := cur_pge_ptr ;
  2672.       advance(cur_pge_ptr) ;
  2673.       incr(size) ;
  2674.       if size = max_line_size then
  2675.       warn('Excessively long line ') ;
  2676.         @.Error: Excessively long line@>
  2677.     until ( ( L_eof( cur_pge) )
  2678.       or (PR_v_next <> image(cur_pge_ptr).vpos ) ) ;
  2679.     next(tail) := zzz ;
  2680.     read_line := head ;
  2681.   end;
  2682. @ These bounds are put in to catch runaway arguments.
  2683. @<Const...@>=
  2684.   page_max = 30000 ;
  2685.   max_line_size = 1000;
  2686.   left_stop = 0 ;
  2687.   deepest = 10 ;
  2688. @ These variables all denote the printer fonts, etc.
  2689. @<Glob...@>=
  2690.   PR_v, PR_v_next,
  2691.   PR_h, PR_h_next,
  2692.   PR_font : i_word ;
  2693. @ This procedure tries to print a line. The main difficulties are: we dont
  2694. want to |Backfeed| unless absolutely necessary; and we may have to deal with
  2695. overstruck characters. One possible way is to shunt them aside somewhere, then
  2696. print the |overflow| after the main line has been printed.
  2697. @<Medium...@>=
  2698.   procedure do_line (line_ptr: link; depth: integer);
  2699.     var overflow : link; 
  2700.   begin
  2701.     @<Move printer vertically to |PR_v_next|, update |PR_v|@>;
  2702.     overflow := zzz ; 
  2703.     while (line_ptr<>zzz) do
  2704.       @<Process the character that |line_ptr| points to, and |advance| to
  2705.       the next@>;
  2706.     if ( overflow = zzz) then 
  2707.       begin @<End the line, trying very hard not to over-feed the paper@> end
  2708.     else begin @<Print the |overflow|@> end;
  2709.     @<Reset printer at end of line, if necessary@>
  2710.   end;
  2711. @ We are actually getting almost in sight of the printer!!! Before we can
  2712. actually print a character, we must first check if it has to go to the
  2713. |overflow|...
  2714. @<Process the char...@>=
  2715.   with image(line_ptr) do begin
  2716.       PR_h_next := hpos - H_shunt ;
  2717.       if not b_space_absolute and not b_space_by_string and
  2718.         (PR_h_next < PR_h) then
  2719.         begin
  2720.           if (PR_h < left_stop) then warn ('Negative H-pos') ;
  2721.           if not batch_view then begin
  2722.               next(overflow) := line_ptr ;
  2723.               advance(overflow) ;
  2724.             end;
  2725.           advance(line_ptr) ;
  2726.         end
  2727.       else begin
  2728.           @<Set horizontal position for the next character@>;
  2729.           if  symbol.IM_font <> PR_font then set_PR_font(symbol.IM_font);
  2730.           print(zchr(symbol.IM_char )) ;
  2731.           PR_h := PR_h + symbol.breadth ;
  2732.           step_wipe(line_ptr ) ;
  2733.         end ; end;
  2734.     @.Error: Negative H-pos@>
  2735. @ If the |overflow| is non-empty, we print it by calling |do_line| recursively
  2736. on it. But sometimes |do_line| tries to recurse to infinite depth (this is a
  2737. bug), so we perform a check first.  
  2738. @<Print the |overflow|@>=
  2739.   @<Return@> ;
  2740.   if (depth < deepest) then begin
  2741.       next(overflow) := zzz ;
  2742.       overflow := next(zzz) ;
  2743.       do_line( overflow, depth + 1);
  2744.     end else warn( 'I am out of my depth') ;
  2745.     @.Error: I am out of my depth@>
  2746. @* Downloading. Not started yet.
  2747. @<Download a whole font@>= do_nothing
  2748. @ @<Enter a download...@>=
  2749. @* Carriage control.
  2750. Once the superior software has decided where the printer has to move to next,
  2751. this section has the job of translating the desired position into elementary
  2752. printer commands. Clearly this mapping depends very much on the range of
  2753. functions that the printer can perform. So this section is controlled by
  2754. several booleans; each asserts that the printer can do the
  2755. corresponding action. Here is a list of the most important ones:\item
  2756. |@!cr_feed_dist| is the distance in |v_steps| by which a carriage-return
  2757. feeds the paper.\item
  2758. |@!wl_feed_dist| ditto, |write_ln|. Similarly for the other |dist|
  2759. values.\item
  2760. |@!feed_absolute| says the printer has an absolute position command that takes
  2761. a parameter |IM_y|, say, and moves to position |IM_y v_steps| down the
  2762. page.\item
  2763. |@!b_feed_absolute| ditto, backfeeding.\item
  2764. |@!b_feed_by_string| says the printer has a |Backfeed| character that moves it
  2765. back by a fixed number |b_feed_dist| of |v_steps|. These booleans should not
  2766. be set true unless the printer can backfeed reliably.\item
  2767. |@!space_absolute| etc., Ditto, horizontal moves.\item
  2768. |@!abs_is_incr| says that in the absolute position commands, the parameter is
  2769. actually an incremental move.\item
  2770. |@!wl_does_cr| says that |write_ln| forces a carriage return.
  2771. In Version 3, I have changed all these constants into variables. I
  2772. hope this will make it easier to support devices that closely resemble
  2773. lineprinters without a lot of messy recompilation.  As mentioned
  2774. above, it is essential to avoid premature line feeds as much as
  2775. possible. Also, many operating systems will choke if the output record
  2776. gets too long, so we must do a |print_ln| at intervals. This program
  2777. tries to accommodate various types of carriage control, some of which
  2778. are not in use at our site. This means that several pieces of
  2779. code have not been tested. Installers must expect to find that the procedures
  2780. defined here will need to be carefully studied in conjunction with the
  2781. I/O section of their \PASCAL\ manual. 
  2782. Now consider what happens at the end of each line. We will want to do a
  2783. subset of the following things: carriage-return, print the |overflow|, line
  2784. feed, split output records. We must keep a clear separation between these
  2785. tasks, and we want to do them in the stated order (but not if |fortran|). This
  2786. order puts most of the carriage controls to the ends of the  output records,
  2787. and (on our machine) makes it easier to examine the output file with an editor.
  2788. So first: do we want to do  carriage-return? If so, then the natural way is to
  2789. print a carriage-return, but not if it will over-feed the paper.
  2790. @<End the line...@>=
  2791.   if( (not wl_does_cr)      {Return is compulsory}
  2792.       or feed_absolute
  2793.       or ((not want_split)  {We can choose C-R or W-L}
  2794.         and (b_space_absolute
  2795.           or ( cr_feed_dist < wl_feed_dist)) {and C-R is preferable}))
  2796.   then begin @<Return@> end;
  2797. @ @<Reset printer...@>= {hook}
  2798. @ Now we decide whether to do any |line_feed|s. But first, we may have to
  2799. attempt to |Backfeed|. Sometimes the program will fail; it should not do so
  2800. unless the \.{DVI} file calls for overstruck characters and the printer
  2801. genuinely cannot do them. If |b_feed_scream|, then print an error message.
  2802. @<Move printer...@>=
  2803.   if (PR_v_next < PR_v) then begin
  2804.     if b_feed_absolute then set_v_abs(PR_v_next)
  2805.     else if b_feed_by_string then
  2806.     while (PR_v_next < PR_v) do @<Backfeed@>
  2807.     else if b_feed_scream then begin
  2808.       warn('Printer cant feed backwards');
  2809.         @.Error: Printer cant...@>
  2810.       display_ln('approximate vertical position is: ', PR_v_next);
  2811.       display_ln(' printing over-fed line on line below');
  2812.       display_ln(' ');
  2813.       PR_v := PR_v_next;
  2814.     end;
  2815.   end;
  2816. @ If we avoided over-feeding, we may want to feed forwards.
  2817. @<Move printer...@>=
  2818.   if list or fortran or (wl_feed_dist > 0 ) then
  2819.     while (PR_v + wl_feed_dist <= PR_v_next) do 
  2820.       begin print_ln;
  2821.         @<Check pause@>
  2822.         if fortran then print( feed_char) ;
  2823.         if wl_does_cr then PR_h := 0 ;
  2824.         PR_v := PR_v + wl_feed_dist ;
  2825.         if squash then PR_v := PR_v_next ;
  2826.       end
  2827.   else if feed_absolute then set_v_abs(PR_v_next) 
  2828.   else begin
  2829.       while (PR_v_next >= PR_v + feed_dist) do @<Line feed@>;
  2830.       print_ln ;
  2831.       if wl_does_cr then PR_h := 0 ;
  2832.     end;
  2833.   while (PR_v_next > PR_v) do @<Tiny feed@> ;
  2834. @ We set the horizontal position in a similar way, but we do not need to be so
  2835. paranoid about backspacing as about back-feeding.
  2836. @<Set horiz...@>=
  2837.   if PR_h_next = PR_h then
  2838.   else begin
  2839.     if  (PR_h_next < PR_h) then begin
  2840.       if b_space_absolute then set_h_abs(PR_h_next)
  2841.       else if b_space_by_string then
  2842.       while (PR_h_next < PR_h) do @<Backspace@>;
  2843.     end;
  2844.     if space_absolute and (PR_h_next > PR_h )
  2845.       then set_h_abs(PR_h_next)
  2846.     else begin
  2847.       while (PR_h_next >= PR_h + space_dist) do @<Space@>;
  2848.       while (PR_h_next > PR_h) do @<Tiny space@> ;
  2849.     end;
  2850.   end;
  2851. @* Low level modules for printer control.
  2852. Now we have to translate these elementary printer commands into actual strings
  2853. of characters to be put into |printfile|. Here is the command for setting a
  2854. new printer's font.
  2855. @<Lowest...@>=
  2856.   procedure set_PR_font(new_font:integer) ;
  2857.   begin
  2858.     if (new_font = PR_font) or only_one_font then
  2859.     else
  2860.     begin
  2861.       print_command( font_command, new_font, ctrl_flag) ;
  2862.       PR_font := new_font ;
  2863.     end;
  2864.   end;
  2865. @ Now for |absolute| movements, if the printer can do them. The procedure
  2866. |set_v_abs| moves the printer to position |mm h_steps| below the top of the
  2867. paper. If |abs_is_incr| then the printers `absolute' command is actually an
  2868. incremental command. So the parameter sent to the printer must be decreased by
  2869. |PR_v|.
  2870. @<Forward...@>=
  2871.   procedure set_v_abs(mm: integer) ; forward ;
  2872.   procedure set_h_abs(mm: integer) ; forward ;
  2873. @ @<Lowest...@>=
  2874.   procedure set_v_abs;
  2875.   var new_pos :integer ;
  2876.   begin
  2877.     if abs_is_incr then
  2878.     new_pos := mm - PR_v
  2879.     else new_pos := mm ;
  2880.     print_command( v_abs_com, new_pos, ctrl_flag) ;
  2881.     PR_v := mm ;
  2882.   end;
  2883.   procedure set_h_abs;
  2884.   var new_pos :integer ;
  2885.   begin
  2886.     if abs_is_incr then
  2887.     new_pos := mm - PR_h
  2888.     else new_pos := mm ;
  2889.     print_command( h_abs_com, new_pos, ctrl_flag) ;
  2890.     PR_h := mm ;
  2891.   end;
  2892. @ Now consider commands for printers that can only do simple movements. A
  2893. |tiny| movement is usually a movement of one |h_step| or |v_step|. All these
  2894. modules should be protected, so they cannot be called unless the printer can
  2895. actually do the stated movement. Normally, the command strings for these are
  2896. only simple characters, so we can just |print| them.
  2897. If |run_on| is false, we want a formfeed between two pages.  |@!page_gap| will
  2898. be true after the end of the first printed page.
  2899. @<Maybe a formfeed@>=
  2900.   if run_on then begin
  2901.       write_ln(printfile);
  2902.       write(printfile,'------ PAGE ', counter[0]:1,' ');
  2903.       write(printfile,'----------------------------------');
  2904.       write(printfile,'----------------------------------');
  2905.       write_ln(printfile);
  2906.     end 
  2907.   else if page_gap then
  2908.     page(printfile) 
  2909.   else page_gap := true ;
  2910.   if is_header then
  2911.     string_print (page_top);
  2912. @ @<Backfeed@>=
  2913.   begin string_print(b_feed_string);
  2914.     PR_v:=PR_v - b_feed_dist;
  2915.   end
  2916. @ @<Line feed@>=
  2917.   begin
  2918.     print(feed_char);
  2919.     if squash then PR_v := PR_v_next
  2920.     else PR_v:=PR_v+feed_dist;
  2921.   end;
  2922. @ @<Tiny feed@>=
  2923.   begin print(t_feed_char); PR_v:=PR_v+t_feed_dist; end;
  2924. @ @<Return@>=
  2925.   if fortran then begin
  2926.       print_ln ; print( cr_char) ; PR_h := 0;
  2927.       PR_v := PR_v + cr_feed_dist ; end
  2928.   else if (b_space_absolute and 
  2929.       ((cr_feed_dist > 0) or (l_margin > 0)) )
  2930.   then set_h_abs(0)
  2931.   else begin
  2932.       print(cr_char);
  2933.       PR_h := 0;
  2934.       PR_v := PR_v + cr_feed_dist ;
  2935.     end ;
  2936. @ @<Backspace@>=
  2937.   begin print (b_space_char); PR_h:=PR_h - b_space_dist; end;
  2938. @ @<Space@>=
  2939.   begin print (space_char); PR_h:=PR_h+space_dist; end;
  2940. @ @<Tiny space@>=
  2941.   begin print (t_space_char); PR_h:=PR_h+t_space_dist; end;
  2942. @* Default declarations for printer.
  2943. This section defines masses of data to describe how the printer behaves.
  2944. Previously this was all in the lineprinter change file. However most Change
  2945. files are for lineprinters, so I moved this stuff into the main program. In V3 I
  2946. changed most of these constants into variables.  The assumed characteristics of
  2947. a lineprinter are as follows:
  2948. 1. A lineprinter can print all the printable ASCII characters, and no others.
  2949. 2. Each character is one |step| high and one |step| wide.
  2950. 3. Printer will act correctly if it receives the following ASCII controls:
  2951. line feed, carriage return, space, and form feed. More precisely, the Standard
  2952. specifies that the \PASCAL\ procedure |page| does something that advances
  2953. the printer by one page.
  2954. 4. Backspacing and backfeeding are assumed impossible ; also we do not use
  2955. tabs.
  2956. The first lot of data describes the printer's overall style of carriage control.
  2957. |fortran| means that the carriage control character gets put at the start of the
  2958. line, and it is here assumed that it must be inserted explicitly.  |feed| means
  2959. a vertical movement and |space| horizontal.  Each |thing_char| is the character
  2960. needed to make the printer do the named action. Owing to the rules of
  2961. \.{TANGLE}, the words |back| and |tiny| have to be abbreviated (to avoid
  2962. identifier clashes). 
  2963. @.ASCII@>
  2964. @<Glob...@>=
  2965.   @!device_ID : packed array[1..12] of char ; {Name of device}
  2966.   list, fortran, b_feed_absolute, b_feed_by_string, feed_absolute, 
  2967.   b_feed_scream, b_space_absolute, b_space_by_string, space_absolute, 
  2968.   abs_is_incr, wl_does_cr, want_split, is_header, do_pause, inspection: boolean ;
  2969.   {These say whether the printer can do the named action}
  2970.   wl_feed_dist, cr_feed_dist, feed_dist, t_feed_dist, b_feed_dist,
  2971.   tiny_drop, big_drop, space_dist, t_space_dist, b_space_dist,
  2972.   pause_i, pause_steps: integer ;
  2973.   {Distances that various actions move}
  2974. @#    
  2975.   feed_char, t_feed_char, cr_char, space_char, t_space_char,
  2976.   b_space_char : char ;
  2977.   start_stuff, stop_stuff, page_top, b_feed_string, font_command,
  2978.   v_abs_com, h_abs_com, pause_after: var_string ;
  2979.   {Command chars and strings}
  2980. @ So here are their default values. We believe they are all appropriate for 
  2981. lineprinters on VMS machines. Note that the program makes no attempt to check
  2982. these values for consistency.
  2983. @<Set init...@>=
  2984.   device_ID := 'Lineprinter '; {Pad to 12 chars}
  2985.   list := false ;
  2986.   fortran := false ;
  2987.   b_feed_absolute := false ;
  2988.   b_feed_by_string := false ;
  2989.   feed_absolute := false ;
  2990.   b_feed_scream := true ;
  2991.   b_space_absolute := false ;
  2992.   b_space_by_string := false ;
  2993.   space_absolute := false ;
  2994.   abs_is_incr := false ;
  2995.   wl_does_cr := false ;
  2996.   want_split := true ;
  2997.   is_header := false ; {each page needs a header}
  2998.   do_pause := false ;
  2999.   inspection := false ;
  3000.   wl_feed_dist := 0 ;
  3001.   cr_feed_dist := 0 ;
  3002.   feed_dist := 1 ;
  3003.   t_feed_dist := 1;
  3004.   b_feed_dist := 0;
  3005.   tiny_drop := 50000 ;  {slightly less than a point}
  3006.   big_drop := 4 ;
  3007.   space_dist := 1;
  3008.   t_space_dist := 1;
  3009.   b_space_dist := 1;
  3010.   pause_i:=0 ;
  3011.   pause_steps := 20 ;
  3012.   feed_char := chr(10) ;
  3013.   t_feed_char := chr(10);
  3014.   cr_char := chr(13);
  3015.   space_char := chr(32) ;
  3016.   t_space_char := chr(32) ;
  3017.   b_space_char := chr(8);
  3018.   start_stuff := blank ;
  3019.   stop_stuff := blank ;
  3020.   page_top  := blank ;
  3021.   b_feed_string := blank ;
  3022.   font_command:= blank ;
  3023.   v_abs_com:= blank ; 
  3024.   h_abs_com:= blank ;    
  3025.   pause_after := blank ;
  3026. @ The general run of \TeX\ characters are narrower than line-printer chars. So
  3027. we spread them out to make them fit.
  3028. @<Set init...@>=
  3029.   l_margin := 1.0 ; {Normal left margin, in inches}
  3030.   top_margin := 1.0 ; {Top ditto}
  3031.   h_fudge := 7.227 {number of points per |h_step|}
  3032.   / 5.25 ; {A typical design width}
  3033.   v_fudge := 2.0 ;
  3034.   { Force double-spacing, in hope that suffixes will come out right}
  3035. @ @<Glob...@>=
  3036.   h_fudge, v_fudge, l_margin, top_margin: real_number ;
  3037. @ Of course, these will have to be changed if |fortran|, or on a system
  3038. that does not use ASCII codes.
  3039.   @.ASCII@>
  3040. @<Set up for...@>=
  3041.   if inspection then begin
  3042.       batch_view := true ;
  3043.       quiet := true ;
  3044.       do_pause := (pause_steps > 0) ;
  3045.     end;
  3046.   if batch_view then begin
  3047.       device_ID := 'screenview  '; {Pad to 12 chars}
  3048.       run_on := true ;
  3049.       fortran := false ;
  3050.       list := true ;
  3051.     end;
  3052.   if fortran then begin
  3053.       wl_does_cr := true ;
  3054.       wl_feed_dist := 1;
  3055.       feed_char := ' ' ;
  3056.       t_feed_char := ' ' ;
  3057.       cr_char := '+' ;
  3058.     end;
  3059.   if list then begin
  3060.       wl_feed_dist := 1;
  3061.       cr_feed_dist := 1 ;
  3062.       cr_char := ' ' ;
  3063.       wl_does_cr := true ;
  3064.       l_margin :=  0 ;
  3065.       top_margin := 0 ;
  3066.     end ;
  3067. @ |start_stuff|  and |stop_stuff| get written into the start and end of
  3068. |printfile|. They are intended to: set printer into correct state for \TeX\
  3069. output, and reset printer to standard state afterwards. If the printer needs
  3070. to be re-initialised in any way at the top of each page, then set |@!page_top|
  3071. to the necessary data and set |is_header| to |true|.
  3072. @<Open |printfile|@>=
  3073.   string_print(start_stuff) ;
  3074.   print_ln ;
  3075. @ @<Clean up afterwards@>=
  3076.   string_print(stop_stuff);
  3077.   print_ln ;
  3078.   if not quiet then begin
  3079.     display ('Output written to file:  ');
  3080.     string_show(print_name);
  3081.   end;
  3082.   display_ln(' ') ;
  3083.     @.Output written...@>
  3084. @ This batch is concerned with distances and resolutions.
  3085. @<Const...@>=
  3086.   h_resolution = 10 ;         {|h_steps| per inch}
  3087.   v_resolution = 6 ;          {|v_steps| per inch}
  3088.   fixed_width = true ;        {printers characters are fixed width}
  3089.   char_width = 1 ;
  3090.   {all printer characters are this width, in units of |h_step|. Normally,
  3091.     |space_dist| will be equal to this, but some printers are not normal!}
  3092.   gap_width = 1 ; {Intended minimum space between words}
  3093.   char_ht = 1 ;
  3094. @ Next, some constants for rule-setting.
  3095. @<Rule setting const...@>=
  3096.   rail_width = 1 ;  {Height and width of rule chars, in steps}
  3097.   rail_height  = 1 ;
  3098.   post_width = 1 ;
  3099.   post_height = 1 ;
  3100.   rail_types = 2 ;
  3101. @ @<Set rule characters@>=
  3102.   rail_chars[1] := codes[ 1, 95 ] ;
  3103.   rail_chars[2] := codes[ 1, 45 ] ;
  3104.   post_char := codes[ 1, 124] ;
  3105. @ The next batch are concerned with fonts.
  3106. @<Const...@>=
  3107.   min_font = 1 ;
  3108.   {smallest and largest number of printers resident fonts}
  3109.   max_font = 1 ;
  3110.   only_one_font = true ;
  3111.   can_dl_font = false ;
  3112.   min_dl_font = 0 ;
  3113.   max_dl_font = 0 ; {printers down-loadable fonts}
  3114.   max_codes = 60 ; {no. of known \TeX\ coding schemes}
  3115.   max_plain = 4 ;  {Max number of a plain text font}
  3116. @ Here is a first attempt at a screenview version based on code by J. Warbrick,
  3117. A. Trevorrow and others. In principle this shows how to switch \.{Crudetype}
  3118. between different devices by flags in the command line.
  3119. @<If the |key| is valid, do the corresponding command@>=
  3120.   else if ( key = "B") then @!batch_view := true 
  3121.   else if ( key = "I") then begin
  3122.       @!inspection := true ;
  3123.       pause_i := s_to_i( pause_steps, false) ;
  3124.       if ((pause_i < 0) or (pause_i > 200) ) then
  3125.         string_warn ('Illegal value for I flag') 
  3126.       else pause_steps := pause_i  ;
  3127.       pause_i:=0 ;
  3128.     end 
  3129.       @.Error: Illegal value@>
  3130. @* Character code data for the printer.
  3131. Here we actually put data into the |codes| array. In general, I have merely
  3132. replaced each character in the PLAIN.TEX coding schemes by the nearest
  3133. equivalent in ASCII, when a reasonable one exists.  For each scheme in turn,
  3134. first give the scheme names, then the character codes.  Scheme 1 is EXTENDED
  3135. ASCII.
  3136. @<Define Lineprinter codes@>=
  3137.   be_string('TEX EXTENDED ASCII' ); set_scheme(1) ;
  3138.   alphabet( 32, 95, 1, 1, 32) ;
  3139.   be_string(  ' .       {|v}    Z       Z       &       ~       {LC-}   Z           ') ;
  3140.   row( 1,0,1) ;
  3141.   be_string(  ' Z       Z       Z       {^|}    {+_}    {LO+}   Z       Z           ') ;
  3142.   row( 1,1,1) ;
  3143.   be_string(  ' Z       Z       Z       Z       Z       Z       {LOx}   Z           ') ;
  3144.   row( 1,2,1) ;
  3145.   be_string(  ' [{L<-}-] [-{-L>}] {=/}  Z       {L<_}   {L>_}   {=_}    [or]        ') ; 
  3146.   row( 1,3,1) ;
  3147. @ Now do scheme 2 = TYPEWRITER. Unfortunately, \.{TANGLE} imposes a limit of 69
  3148. on the length of quoted strings. This causes difficulty because several multi-
  3149. character commands are too long for 8 of them to fit neatly into a string of
  3150. that length. So I use the Z command to ( effectively) split any complicated
  3151. |row_spec| into two.
  3152. @<Define Lineprinter codes@>=
  3153.   be_string('TEX TYPEWRITER TEXT' ); set_scheme(2) ;
  3154.   alphabet( 32, 95, 2, 1, 32) ;
  3155.   codes[2, 127].IM_char:= 34 ;
  3156.   be_string(  ' Z               [{/_}{_\}]      {0-}            [/\]        Z Z Z Z ') ;
  3157.   row( 2,0,1) ;
  3158.   {first half row}
  3159.   be_string(  ' Z Z Z Z         <U_{-_}>        <U[__][||]>     <U_{L>_}>        LY ') ;
  3160.   row( 2,0,1) ;
  3161.   {and second}
  3162.   be_string(  ' {oI}    {u|}    Z       {^|}    {v|}    Q       !       ?           ') ;
  3163.   row( 2,1,1) ;
  3164.   be_string(  ' i       j       `       Q       Z       Z       <U_>    <U.>        ') ;
  3165.   row( 2,2,1) ;
  3166.   be_string(  ' ,       {LB_}   [ae]    [oe]    {o/}    [LALE]  [LOLE]  {LO/}       ') ;
  3167.   row( 2,3,1) ;
  3168. @ The TEX TEXT scheme is nearly the same, so we start by copying it.
  3169. @<Define Lineprinter codes@>=
  3170.   be_string('TEX TEXT' ); set_scheme(3) ;
  3171.   for in_i := 0 to 127 do codes [3, in_i] := codes [2, in_i] ;
  3172.   be_string(  ' Z       Z       Z       [ff]    [fi]    [fl]    [ffi]   [ffl]       ') ;
  3173.   row( 3,1,1) ;
  3174.   be_string(  ' Z       Z       Z       Z       !       Z       ?       Z           ') ;
  3175.   row( 3,7,1) ;
  3176.   be_string(  ' Z       Z       Z       Z       W       Z       Z       <U.>        ') ;
  3177.   row( 3,11,1) ;
  3178.   be_string(  ' Z       Z       Z       -       [--]    W       ~       W           ') ;
  3179.   row( 3,15,1) ;
  3180. @ Recently there has appeared a scheme, called TEX TEXT WITHOUT F-LIGATURES.
  3181. @<Define Lineprinter codes@>=
  3182.   be_string('TEX TEXT WITHOUT F-LIGATURES' ); set_scheme(4) ;
  3183.   for in_i := 0 to 127 do codes [4, in_i] := codes [3, in_i] ;
  3184.   be_string(  ' {oI}    {u|}    Z       {^|}    {v|}    Q       !       ?           ') ;
  3185.   row( 4,1,1) ;
  3186. @ and AEFMNOT ONLY  (for the Metafont logo).
  3187. @<Define Lineprinter codes@>=
  3188.   be_string('AEFMNOT ONLY' ); set_scheme(5) ;
  3189.   be_string(  ' Z      LA       Z       Z       Z      LE      LF       Z           ') ;
  3190.   row( 5,8,1) ;
  3191.   be_string(  ' Z       Z       Z       Z       Z      LM      LN      LO           ') ;
  3192.   row( 5,9,1) ;
  3193.   be_string(  ' Z       Z       Z       Z      LT       Z       Z       Z           ') ;
  3194.   row( 5,10,1) ;
  3195. @ The MATH ITALIC scheme is almost impossible.
  3196. @<Define Lineprinter codes@>=
  3197.   be_string('TEX MATH ITALIC' ); set_scheme(6) ;
  3198.   for in_i := 0 to 9 do codes [6, in_i] := codes [2, in_i] ;
  3199.   alphabet( 48, 43, 6, 1, 48) ;
  3200.   alphabet( 97, 26, 6, 1, 97) ;
  3201.   be_string(  ' Z       Z       .       ,       Z       /       Z       *           ') ;
  3202.   row( 6,7,1) ;
  3203. @ MATH SYMBOLS are messy, and no doubt the results will look unpleasant.
  3204. @<Define Lineprinter codes@>=
  3205.   be_string('TEX MATH SYMBOLS' ); set_scheme(7) ;
  3206.   alphabet( 65, 26, 7, 1, 65) ;
  3207.   be_string(  ' -       .       {\/}    *       {-:}    Z       {+_}    <U_+>       ') ;
  3208.   row( 7,0,1) ;
  3209.   be_string(  ' {LO+}   {LO-}   {LOx}   {LO/}   LO      LO       o       o          ') ;
  3210.   row( 7,1,1) ;
  3211.   be_string(  ' Z       {=_}    Z       Z       {L<_}   {L>_}   {L<_}   {L>_}       ') ;
  3212.   row( 7,2,1) ;
  3213.   be_string(  ' ~       <U~~>   Z       Z       [L<L<]  [L>L>]  L<      L>          ') ;
  3214.   row( 7,3,1) ;
  3215.   be_string(  ' [{L<-}-]        [-{L>-}]        {|^}            {|v}    Z Z Z Z     ') ;
  3216.   row( 7,4,1) ;
  3217.   be_string(  ' Z Z Z Z         [{L<-}{-L>}]    /               \       {~_}        ') ;
  3218.   row( 7,4,1) ;
  3219.   be_string(  ' [{L<=}=]        [={L>=}]        {|^}            {|v}    Z Z Z Z     ') ;
  3220.   row( 7,5,1) ;
  3221.   be_string(  ' Z Z Z Z         [{L<=}{=L>}]    Z               Z       Z           ') ;
  3222.   row( 7,5,1) ;
  3223.   be_string(  ' Q               [oo]            {L(-}           {-L)}   Z Z Z Z     ') ;
  3224.   row( 7,6,1) ;
  3225.   be_string(  ' Z Z Z Z         [{/_}{_\}]      <U[__][\/]>     /       Q           ') ;
  3226.   row( 7,6,1) ;
  3227.   be_string(  ' [{\-}{-/}]      <U_[{-_}|]>     ~       {0/}    LR LT   <U_|>   {|_}') ;
  3228.   row( 7,7,1) ;
  3229.   be_string(  ' Z       Z       Z       LU      Z       {LU+}   &       [or]        ') ;
  3230.   row( 7,11,1) ;
  3231.   be_string(  ' [{|-}-]         [-{-|}]         Z Z Z   Z       L{      L}          ') ;
  3232.   row( 7,12,1) ;
  3233.   be_string(  ' L<      L>      |       [||]    {^|v}   {^|v}   \       Z           ') ;
  3234.   row( 7,13,1) ;
  3235.   be_string(  ' <[S_]{v/}>      [{LI_}{LI_}]    <U[__][\/]>     <U/|/>  Z Z Z Z     ') ;
  3236.   row( 7,14,1) ;
  3237.   be_string(  ' {LS*}           {|-}            {|-_}           {9|}    Z Z Z Z     ') ;
  3238.   row( 7,15,1) ;
  3239. @ And here is a first attempt at the MATH EXTENSION scheme. These codes look
  3240. rather peculiar because characters in the Extension font (unlike all others)
  3241. have their reference points at the top. Here the restriction of string length
  3242. to 69 is a real pain. It seemed that the least bad way to arrange these |row|s
  3243. was by splitting each into 2 halves; then most half-rows do fit into one
  3244. |row_string|. Here are the left hand halves.
  3245. @<Define Lineprinter codes@>=
  3246.   be_string('TEX MATH EXTENSION' ); set_scheme(8) ;
  3247.   be_string(' <SL(L(>         <SL)L)>         <SL[L[>         <SL]L]>         ZZZZ') ;
  3248.   row( 8,0,1);
  3249.   be_string(' <SL{L{>         <SL}L}>         <S/\>           <S\/>           ZZZZ') ;
  3250.   row( 8,1,1);
  3251.   be_string(' <SL(L(L(>       <SL)L)L)>       <SL(L(L(L(>     <SL)L)L)L)>     ZZZZ') ;
  3252.   row( 8,2,1);
  3253.   be_string(' <SL[|||>        <SL]|||>        <SL{/\L{>       <SL}\/L}>       ZZZZ') ;
  3254.   row( 8,3,1);
  3255.   be_string(' <SL(L(L(L(L(>   <SL)L)L)L)L)>   <SL[|||L[>      <SL]|||L]>      ZZZZ') ;
  3256.   row( 8,4,1);
  3257.   be_string(' <SL{L|L<|L{>    <SL}|L>|L}>     <S[S/]/\[S\]>   <S\[S\][S/]/>   ZZZZ') ;
  3258.   row( 8,5,1);
  3259.   be_string(' <S/||>          <S\||>          <SL[||>         <SL]||>         ZZZZ') ;
  3260.   row( 8,6,1);
  3261.   be_string(' <S[S/]|>        <S\[S|]>        <S|[S\]>        <S[S|]/>        ZZZZ') ;
  3262.   row( 8,7,1);
  3263.   be_string(' <S||\>          <S||/>          <S|>            <S|>            ZZZZ') ;
  3264.   row( 8,8,1);
  3265.   be_string(' <[__]\[{/_}_]>  <[__][LILI]>    </|/>           <S[||][\/]>     ZZZZ') ;
  3266.   row( 8,10,1);
  3267.   be_string(' <S[{|_}{|_}]>   <S[|S|][{|_}_{_|}]>     <U_>    <U[__]>         ZZZZ') ;
  3268.   row( 8,12,1);
  3269.   be_string(' <SL[L[L[>       <SL]L]L]>       <S||L[>         <S||L]>         ZZZZ') ;
  3270.   row( 8,13,1);
  3271.   be_string(' <S{|^}>         <S{|v}>         /               \               ZZZZ') ;
  3272.   row( 8,15,1);
  3273. @ Here are the right hand halves.
  3274. @<Define Lineprinter codes@>=
  3275.   be_string('ZZZZ   <S|L[>          <S|L]>          <SL[|>          <SL]|>        ') ;
  3276.   row( 8,0,1);
  3277.   be_string('ZZZZ   <S|>            <S[||]>         <S[S/]/>        <Z\[S\]>      ') ;
  3278.   row( 8,1,1);
  3279.   be_string('ZZZZ   <SL[L[L[L[>     <SL]L]L]L]>     <S|||L[>        <S|||L]>      ') ;
  3280.   row( 8,2,1);
  3281.   be_string('ZZZZ   <S[S/]/\[S\]>   <S\[S\][S/]/>   <S[S/][S/]//>   <S\\[S\][S\]> ') ;
  3282.   row( 8,3,1);
  3283.   be_string('ZZZZ   <S||||L[>       <S||||L]>       <SL[||||>       <SL]||||>     ') ;
  3284.   row( 8,4,1);
  3285.   be_string('ZZZZ   <S[S/][S/]//>   <S\\[S\][S\]>   <S[SS/][S/]/>   <S\[S\][SS\]> ') ;
  3286.   row( 8,5,1);
  3287.   be_string('ZZZZ   <S||L[>         <S||L]>         <S|>            <S|>          ') ;
  3288.   row( 8,6,1);
  3289.   be_string('ZZZZ   <S[S|]L<[S|]>   <S|[SL>]|>      <S|>            <S|>          ') ;
  3290.   row( 8,7,1);
  3291.   be_string('ZZZZ   <S[S/]L<[S\]>  <S\[SL>]/>  <S[{|_}{|_}]>  <S[|S|][{|_}S{|_}]> ') ;
  3292.   row( 8,8,1);
  3293.   be_string('ZZZZ   <S[/\][||]>     <S[|+|][\_/]>   <S[/\]>         <S[\/]>       ') ;
  3294.   row( 8,10,1);
  3295.   be_string('ZZZZ   <U[___]>        <U~>            <U[~~]>         <U[~~~]>      ') ;
  3296.   row( 8,12,1);
  3297.   be_string('ZZZZ   <SL[||>         <SL]||>         <SL{L{L{>       <SL}L}L}>     ') ;
  3298.   row( 8,13,1);
  3299.   be_string('ZZZZ   \               /               <S{|^}>         <S{|v}>       ') ;
  3300.   row( 8,15,1);
  3301. @ And here are the half rows that are so long that even half a row must be
  3302. further split.
  3303. @<Define Lineprinter codes@>=
  3304.   be_string('        </{|O}/>   <S/{|O}|/>   <S[/\][\/]>   <S[S_][/.\][\_/]>  ZZZZ') ;
  3305.   row( 8,9,1);
  3306.   be_string('ZZZZ    <S[{/_}{\_}][\/]>       <S[S_][{/_}{|_}{_\}][\{|_}/]>      ZZ') ;
  3307.   row( 8,9,1);
  3308.   be_string('ZZZZZZ  <S[{\/}{/\}][{\/}{/\}]> <[S_][{\/}S{/\}][|{\/}|][{/\}_{/\}]> ') ;
  3309.   row( 8,9,1);
  3310.   be_string('        <[___]\[SL>][{/_}__]>   <[___][|S|][|S|][|S|]> <S/||/>  ZZZZZ') ;
  3311.   row( 8,11,1);
  3312.   be_string('ZZZ     <S[|S|][|S|][\_/]>      <[S_][/S\][|S|][|S|]>             ZZZ') ;
  3313.   row( 8,11,1);
  3314.   be_string('ZZZZZ   <S[|S|][|+|][\_/]>      <SS[S/\][/SS\]>  <SS[\SS/][S\/]>     ') ;
  3315.   row( 8,11,1);
  3316.   be_string('  <S[_S|][S\|]>   <S[SS|][_S|][S\|]>  <S[SS|][SS|][_S|][S\|]>   ZZZZZ') ;
  3317.   row( 8,14,1);
  3318.   be_string('ZZZ     <S[SS|][SS|][SS|][_S|][S\|]>  <S[SS|][_S|][S\|]>          ZZZ') ;
  3319.   row( 8,14,1);
  3320.   be_string('ZZZZZ   <S|>      <U_|>               <S[||]>                        ') ;
  3321.   row( 8,14,1);
  3322. @ The LATEX SYMBOL and LATEX CIRCLE schemes are really only included to stop
  3323. the silly error messages that appear when processing LaTeX documents if they
  3324. are omitted.  But having said that, there are one or two characters that we
  3325. can do: (This LATEX and ADOBE stuff by courtesy of J.Warbrick.)
  3326. @<Define Lineprinter codes@>=
  3327.   be_string( 'LATEX SYMBOLS' ); set_scheme(9) ;
  3328.   be_string( 'LATEX CIRCLE' ); set_scheme(10) ;
  3329.   be_string(' Z       [L<|]          [{L<_}|]        [|L>]    [|{L>_}]  Z Z Z     ') ;
  3330.   row( 9, 0,1) ;
  3331.   be_string(' [L<]    [L>]           Z               Z        Z         Z Z Z     ') ;
  3332.   row( 9, 5,1) ;
  3333.   be_string(' Z       Z              ~               ~        Z         Z Z Z     ') ;
  3334.   row( 9, 7,1) ;
  3335. @ LATEX LINE is much the same, except we can try more of the characters.
  3336. Since the line segments will not line up correctly, the results of using this
  3337. will probably be nasty
  3338. @<Define Lineprinter codes@>=
  3339.   be_string( 'LATEX LINE' ); set_scheme( 11) ;
  3340.   be_string(' /       |       |       |       |       |       Z       Z           ') ;
  3341.   row( 11, 0,1) ;
  3342.   be_string(' /       L<      /       v       |       v       ^       v           ') ;
  3343.   row( 11, 1,1) ;
  3344.   be_string(' /       /       L>      /       /       ^       Z       ^           ') ;
  3345.   row( 11, 2,1) ;
  3346.   be_string(' /       L<      /       L<      /       L<      L>      Z           ') ;
  3347.   row( 11, 3,1) ;
  3348.   be_string(' /       /       /       /       Z       /       Z       Z           ') ;
  3349.   row( 11, 4,1) ;
  3350.   be_string(' _       L<      L>      L<      /       L>      Z       L<          ') ;
  3351.   row( 11, 5,1) ;
  3352.   be_string(' Z       L>      Z       L>      Z       Z       ^       L>          ') ;
  3353.   row( 11, 6,1) ;
  3354.   be_string(' Z       L<      L>      Z       Z       L<      L>      v           ') ;
  3355.   row( 11, 7,1) ;
  3356.   be_string(' \       |       |       |       |       |       Z       Z           ') ;
  3357.   row( 11, 8,1) ;
  3358.   be_string(' \       L<      \       ^       |       ^       v       ^           ') ;
  3359.   row( 11, 9,1) ;
  3360.   be_string(' \       \       L>      \       \       v       Z       v           ') ;
  3361.   row( 11,10,1) ;
  3362.   be_string(' \       L<      \       L<      \       L<      L>      Z           ') ;
  3363.   row( 11,11,1) ;
  3364.   be_string(' \       \       \       \       Z       \       Z       Z           ') ;
  3365.   row( 11,12,1) ;
  3366.   be_string(' _       L<      L>      L<      \       L>      Z       L<          ') ;
  3367.   row( 11,13,1) ;
  3368.   be_string(' Z       L>      Z       L>      Z       Z       Z       L>          ') ;
  3369.   row( 11,14,1) ;
  3370.   be_string(' Z       L<      L>      Z       Z       L<      L>      Z           ') ;
  3371.   row( 11,15,1) ;
  3372. @ Next comes ADOBESTANDARDENCODING for PostScript text fonts.  This assumes
  3373. that your PostScript font TFM's use this as the coding scheme name for the
  3374. font. AdobeStandardEncoding is what Adobe call their text encoding, so you
  3375. will be OK if this got copied across.
  3376. @<Define Lineprinter codes@>=
  3377.   be_string( 'ADOBESTANDARDENCODING' ); set_scheme( 12) ;
  3378.   alphabet(32,95,12,1,32);
  3379.   be_string(' Z       !       {LC/}   $       /       {Y=}    f       {LS*}       ') ;
  3380.   row( 12,20,1) ;
  3381.   be_string(' *       Q       W       [L<L<]  L<      L>      [fi]    [fl]        ') ;
  3382.   row( 12,21,1) ;
  3383.   be_string(' Z       -       {|-}    {|-_}   .       Z       {9|}    .           ') ;
  3384.   row( 12,22,1) ;
  3385.   be_string(' ,       [,,]    W       [L>L>]  [...]   [%.]    Z       ?           ') ;
  3386.   row( 12,23,1) ;
  3387.   be_string(' Z       `       Q       ^       ~       <U_>    Z       <U.>        ') ;
  3388.   row( 12,24,1) ;
  3389.   be_string(' <U[..]> Z       <U.>    ,       Z       W       ,       Z           ') ;
  3390.   row( 12,25,1) ;
  3391.   be_string(' [--]    Z       Z       Z       Z       Z       Z       Z           ') ;
  3392.   row( 12,26,1) ;
  3393.   be_string(' Z       [LALE]  Z       <U{a_}> Z       Z       Z       Z           ') ;
  3394.   row( 12,28,1) ;
  3395.   be_string(' L       {O/}    [OLE]   <U{o_}> Z       Z       Z       Z           ') ;
  3396.   row( 12,29,1) ;
  3397.   be_string(' Z       [ae]    Z       Z       Z       i       Z       Z           ') ;
  3398.   row( 12,30,1) ;
  3399.   be_string(' l       {O/}    [oe]    {B_}    Z       Z       Z       Z           ') ;
  3400.   row( 12,31,1) ;
  3401. @ And then ADOBESYMBOLENCODING for PostScript symbols. Adobe flag the coding
  3402. scheme for Symbol as FontSpecific, but we can do better than this, so I have
  3403. changed the scheme in the TFM's to this name.
  3404. @<Define Lineprinter codes@>=
  3405.   be_string( 'ADOBESYMBOLENCODING' ); set_scheme( 13) ;
  3406.   alphabet(32,48,13,1,32);
  3407.   be_string(' Z       Z   [{\-}{-/}]  Z   <U_[{-_}|]> Z       Z       {)-}$       ') ;
  3408.   row( 13, 4,1) ;
  3409.   be_string(' {~=}    Z       Z       X    [{/_}{_\}] Z       {oI}    S           ') ;
  3410.   row( 13, 8,1) ;
  3411.   be_string(' Z       Z       v       Z       [/\]    Z       Z       Z           ') ;
  3412.   row( 13, 9,1) ;
  3413.   be_string(' <U[__][||]> {O-} P  <U_{L>_}>   T       Y       Z       Z           ') ;
  3414.   row( 13,10,1) ;
  3415.   be_string(' <U_{-_}> {u|}   LZ      L[ <U[S.][.S.]> L]      {|_}    _           ') ;
  3416.   row( 13,11,1) ;
  3417.   be_string(' <U[S_]> Z       Z       Z       Z       Z       Z       Z           ') ;
  3418.   row( 13,12,1) ;
  3419.   be_string(' Z       Z       Z       L{      |       L}      ~       Z           ') ;
  3420.   row( 13,15,1) ;
  3421.   be_string(' Z       Z       Q       {L<_}   /       [oo]    f       Z           ') ;
  3422.   row( 13,20,1) ;
  3423.   be_string(' Z       Z       Z [{L<-}{-L>}] [{L<-}-] {^|} [-{-L>}]   {|v}        ') ;
  3424.   row( 13,21,1) ;
  3425.   be_string(' o       {+_}    W       {L>_}   x       Z       d       o           ') ;
  3426.   row( 13,22,1) ;
  3427.   be_string(' {-:}    {=/}    {=_}    <U~~>   [...]   <U||>   [---]   Z           ') ;
  3428.   row( 13,23,1) ;
  3429.   be_string(' Z       Z       Z       Z       {Ox}    {O+}    {O/}    Z           ') ;
  3430.   row( 13,24,1) ;
  3431.   be_string(' LU      Z       Z       Z       Z       Z       {(-}    {(-/}       ') ;
  3432.   row( 13,25,1) ;
  3433.   be_string(' {/_} <U[__][\/]> {Or}   {Oc}   [TLM] <U[__][||]> <U[S_]{v/}> .      ') ;
  3434.   row( 13,26,1) ;
  3435.   be_string(' ~       &    [or] [{L<=}{=L>}] [{L<=}=] {^|} [={=L>}]    {|v}       ') ;
  3436.   row( 13,27,1) ;
  3437.   be_string(' Z       <U/\>   {Or}    {Oc}   [TLM] <U_{L>_}> <U[S/]|>  <U||>      ') ;
  3438.   row( 13,28,1) ;
  3439.   be_string(' <U|[S\]> <UL[|> <U||>   <U|L[> <U[S/]|> <U||> <U|[S\]>   <U||>      ') ;
  3440.   row( 13,29,1) ;
  3441.   be_string(' Z     <U\/> <U[S/][S|]/> <U[S/]|> <U||> <U[S|]/> <U\[S|]> <U||>     ') ;
  3442.   row( 13,30,1) ;
  3443.   be_string(' <U[S|]/> <UL]|>  <U||>   <U|L]> <U\[S|]> <U||>   <U[S|]/>  Z        ') ;
  3444.   row( 13,31,1) ;
  3445. @ At our site (RHBNC) we have a set of text and symbol fonts. These were clearly
  3446. made by people who didnt understand what a coding scheme was.  The text fonts
  3447. have all sorts of coding schemes written in their files, like `HELVETICA BOLD'
  3448. etc., but in fact they nearly all actually use TEX TEXT. Some of the symbol
  3449. fonts have the same scheme written into their files as the Zapf fonts, which are
  3450. entirely different. In order to make some sense out of this mess, we arrange
  3451. that an unknown scheme will default to TEX TEXT. This deals with all the text
  3452. fonts. We will use the actual font names for some of the symbol fonts. 
  3453. Also our local \TeX\ is V2; it can only handle 128 char fonts. The PSYMML font
  3454. is the first 128 chars of the symbol font; so we just give it the same scheme
  3455. number. But PSYMMU is the other 128 chars offset by -128. (i.e. char 50 of
  3456. PSYMMU = char 178 of SYMBOL).
  3457. @<Define Lineprinter codes@>=
  3458.   be_string( 'SYMBOL' ); set_scheme( 13) ;
  3459.   be_string( 'PSSYML' ); set_scheme( 13) ;
  3460.   be_string( 'PSSYMU' ); set_scheme( 14) ;
  3461.   for in_i := 0 to 127 do
  3462.     codes [14, in_i] := codes [13, in_i + 128] ;  
  3463. @ For the Screenview version we fudge some of these rows:
  3464. @<Define Lineprinter codes@>=
  3465.   if batch_view then begin
  3466.       be_string(' Z       Z       [\=]    Z       [L<=]   [L>=]   [==]    [or]        ') ; 
  3467.       row( 1,3,1) ;
  3468.       be_string(' Z       [==]    Z       Z       [L<=]   [L>=]   [L<=]   [L>=]       ') ;
  3469.       row( 7,2,1) ;
  3470.     end ;
  3471. @ This peculiar arrangement is intended to allow the HPGF change file 
  3472. to make changes.
  3473. @<Assign char...@>=
  3474.   @<Define Lineprinter codes@>
  3475.   @<Set rule characters@>
  3476. @* Printer changes can be put here.
  3477. *** Attach printer change file here ***
  3478. @* Index.
  3479. Pointers to error messages appear here under ``error" or ``Fatal" together with
  3480. the section numbers where each identifier is used.
  3481.